spree_api 5.2.6 → 5.3.0.rc1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v2/product_list_includes.rb +10 -22
  3. data/app/controllers/concerns/spree/api/v2/storefront/order_concern.rb +1 -1
  4. data/app/controllers/spree/api/v2/platform/classifications_controller.rb +1 -1
  5. data/app/controllers/spree/api/v2/resource_controller.rb +8 -5
  6. data/app/controllers/spree/api/v2/storefront/products_controller.rb +1 -4
  7. data/app/helpers/spree/api/v2/display_money_helper.rb +30 -10
  8. data/app/jobs/spree/webhook_delivery_job.rb +16 -0
  9. data/app/paginators/spree/api/paginate.rb +68 -0
  10. data/app/serializers/spree/api/v2/platform/product_serializer.rb +12 -4
  11. data/app/serializers/spree/api/v2/platform/variant_serializer.rb +12 -4
  12. data/app/serializers/spree/v2/storefront/product_serializer.rb +13 -5
  13. data/app/serializers/spree/v2/storefront/taxon_serializer.rb +1 -1
  14. data/app/serializers/spree/v2/storefront/variant_serializer.rb +12 -4
  15. data/app/services/spree/webhooks/deliver_webhook.rb +83 -0
  16. data/app/subscribers/spree/webhook_event_subscriber.rb +67 -0
  17. data/config/routes.rb +0 -2
  18. data/lib/generators/spree/api/install/install_generator.rb +24 -0
  19. data/lib/spree/api/configuration.rb +3 -0
  20. data/lib/spree/api/dependencies.rb +1 -1
  21. data/lib/spree/api/engine.rb +6 -0
  22. data/lib/spree/api/testing_support/matchers/webhooks.rb +1 -67
  23. data/lib/spree/api/testing_support/spree_webhooks.rb +1 -9
  24. data/lib/spree/api.rb +3 -0
  25. data/lib/spree_api.rb +0 -2
  26. metadata +25 -33
  27. data/app/controllers/spree/api/v2/platform/webhooks/events_controller.rb +0 -25
  28. data/app/controllers/spree/api/v2/platform/webhooks/subscribers_controller.rb +0 -25
  29. data/app/jobs/spree/webhooks/subscribers/make_request_job.rb +0 -17
  30. data/app/models/concerns/spree/webhooks/has_webhooks.rb +0 -95
  31. data/app/models/spree/order/webhooks.rb +0 -39
  32. data/app/models/spree/payment/webhooks.rb +0 -23
  33. data/app/models/spree/product/webhooks.rb +0 -42
  34. data/app/models/spree/shipment/webhooks.rb +0 -19
  35. data/app/models/spree/stock_item/webhooks.rb +0 -40
  36. data/app/models/spree/stock_movement/webhooks.rb +0 -49
  37. data/app/models/spree/variant/webhooks.rb +0 -25
  38. data/app/models/spree/webhooks/base.rb +0 -11
  39. data/app/models/spree/webhooks/event.rb +0 -20
  40. data/app/models/spree/webhooks/event_signature.rb +0 -33
  41. data/app/models/spree/webhooks/subscriber.rb +0 -88
  42. data/app/serializers/spree/api/v2/platform/webhooks/event_serializer.rb +0 -15
  43. data/app/serializers/spree/api/v2/platform/webhooks/subscriber_serializer.rb +0 -13
  44. data/app/services/spree/webhooks/subscribers/handle_request.rb +0 -77
  45. data/app/services/spree/webhooks/subscribers/make_request.rb +0 -83
  46. data/app/services/spree/webhooks/subscribers/queue_requests.rb +0 -27
  47. data/db/migrate/20210902162826_create_spree_webhooks_tables.rb +0 -16
  48. data/db/migrate/20211025162826_create_spree_webhooks_events.rb +0 -14
  49. data/db/migrate/20221221122100_add_secret_key_to_spree_webhooks_subscribers.rb +0 -5
  50. data/db/migrate/20230116204600_backfill_secret_key_for_spree_webhooks_subscribers.rb +0 -5
  51. data/db/migrate/20230116205000_change_secret_key_to_non_null_column.rb +0 -5
  52. data/lib/spree/api/testing_support/factories/webhook_event_factory.rb +0 -27
  53. data/lib/spree/api/testing_support/factories/webhook_subscriber_factory.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '078f95314835c35a1dd304acb901e6354f1a97618ff13e3dfdbe6a0788d75756'
4
- data.tar.gz: 8c8c35d33b999b0f6e369ed8fbfb1b959e14f3aa4bb53b5d9457efcf03f40f5c
3
+ metadata.gz: bf006c6d540a502177ba141362ccc562beb80a4ab19e61894c6cd7d270f3071e
4
+ data.tar.gz: a36baaff83201059e4c4b96b3d97e73c7e6eb8e94644fdc74c67320c4eed9eee
5
5
  SHA512:
6
- metadata.gz: 5dd9343bb4ab4191438247ba04a57862a4cb745a48500b9a90ded2d7c8f74355ac448d1f189a82eb76316fbc04bff0c8190785d56523fa5749632a2b0f14a6a5
7
- data.tar.gz: e67051467b1304370289c55d91bd01eac0924b42a46b7d5b0e6be87544a6e2077abf5b8c9330685f937d9612e5d7ce92355ca03d8c89b5c017aaf50c63d1fa5f
6
+ metadata.gz: c19e194cf63ffcadaaa3b0e3fb32e6a676fe55a8ff22f5352b32508cd67b87b03f8353d4613f10d9fa31bdb8b96e14a58850d86be323b4d10331ead4c917150e
7
+ data.tar.gz: 1894f064a469b4afd3952f04728972486bdac54090e2b0100bb31e6e5a9ce0313a31142499cc1c4fd2a17079fe7ad17d93e37d7bff1f7702fd2675d31ab0896e
@@ -1,31 +1,19 @@
1
1
  module Spree
2
2
  module Api
3
3
  module V2
4
+ # These includes are not picked automatically by ar_lazy_preload gem so we need to specify them manually.
4
5
  module ProductListIncludes
5
6
  def product_list_includes
6
- @product_list_includes ||= {
7
- taggings: [:tag],
8
- variants: [],
9
- master: [:prices]
7
+ {
8
+ option_types: [],
9
+ product_properties: [],
10
+ metafields: [],
11
+ variant_images: [],
12
+ tags: [],
13
+ taxons: [:taxonomy],
14
+ master: [:prices, :images, { stock_items: :stock_location }, metafields: [], option_values: []],
15
+ variants: [:prices, :images, { stock_items: :stock_location }, metafields: [], option_values: []]
10
16
  }
11
-
12
- @product_list_includes[:variant_images] = [] if params[:include]&.match('images')
13
- @product_list_includes[:option_types] = [] if params[:include]&.match('option_types')
14
- @product_list_includes[:product_properties] = [:property] if params[:include]&.match('product_properties')
15
- @product_list_includes[:master] = variant_includes if params[:include]&.match(/master|default_variant/)
16
- @product_list_includes[:variants] = variant_includes if params[:include]&.match(/variants|default_variant/)
17
- @product_list_includes[:taxons] = [:taxonomy, :icon, :store, :rich_text_translations, image_attachment: :blob] if params[:include]&.match('taxons')
18
- @product_list_includes
19
- end
20
-
21
- def variant_includes
22
- variant_includes = {
23
- prices: [],
24
- option_values: :option_type,
25
- stock_items: :stock_location
26
- }
27
- variant_includes[:images] = [] if params[:include]&.match(/images/)
28
- variant_includes
29
17
  end
30
18
  end
31
19
  end
@@ -39,7 +39,7 @@ module Spree
39
39
  end
40
40
 
41
41
  def serialize_order(order)
42
- Spree::Deprecation.warn('OrderConcern#serialize_order is deprecated and will be removed in Spree 6.0. Please use `serialize_resource` method')
42
+ Spree::Deprecation.warn('OrderConcern#serialize_order is deprecated and will be removed in Spree 5.5. Please use `serialize_resource` method')
43
43
  serialize_resource(order)
44
44
  end
45
45
  end
@@ -12,7 +12,7 @@ module Spree
12
12
  def scope_includes
13
13
  [
14
14
  taxon: [],
15
- product: [:variants_including_master, :variant_images, :master, { variants: [:prices] }]
15
+ product: [:variants_including_master, :master, { variants: [:prices] }]
16
16
  ]
17
17
  end
18
18
 
@@ -51,11 +51,14 @@ module Spree
51
51
  end
52
52
 
53
53
  def collection
54
- @collection ||= if defined?(collection_finder)
55
- collection_finder.new(scope: scope, params: finder_params).execute
56
- else
57
- scope
58
- end
54
+ @collection ||= begin
55
+ result = if defined?(collection_finder)
56
+ collection_finder.new(scope: scope, params: finder_params).execute
57
+ else
58
+ scope
59
+ end
60
+ result.preload_associations_lazily
61
+ end
59
62
  end
60
63
 
61
64
  def finder_params
@@ -21,10 +21,7 @@ module Spree
21
21
  end
22
22
 
23
23
  def variant_includes
24
- [
25
- :prices,
26
- { option_values: [:option_type] }
27
- ]
24
+ [:images, :prices, { stock_items: :stock_location }]
28
25
  end
29
26
 
30
27
  def collection_sorter
@@ -5,37 +5,57 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  class_methods do
8
- def find_price(product_or_variant, currency)
9
- product_or_variant.price_in(currency)
8
+ def find_price(product_or_variant, currency, context_options = {})
9
+ variant = product_or_variant.is_a?(Spree::Product) ? product_or_variant.default_variant : product_or_variant
10
+
11
+ if context_options.present?
12
+ context = build_pricing_context(variant, currency, context_options)
13
+ variant.price_for(context)
14
+ else
15
+ variant.price_in(currency)
16
+ end
10
17
  end
11
18
 
12
- def price(product_or_variant, currency)
13
- price = find_price(product_or_variant, currency)
19
+ def price(product_or_variant, currency, context_options = {})
20
+ price = find_price(product_or_variant, currency, context_options)
14
21
  return nil if price.new_record?
15
22
 
16
23
  price.amount
17
24
  end
18
25
 
19
- def display_price(product_or_variant, currency)
20
- price = find_price(product_or_variant, currency)
26
+ def display_price(product_or_variant, currency, context_options = {})
27
+ price = find_price(product_or_variant, currency, context_options)
21
28
  return nil if price.new_record?
22
29
 
23
30
  Spree::Money.new(price.amount, currency: currency).to_s
24
31
  end
25
32
 
26
- def compare_at_price(product_or_variant, currency)
27
- price = find_price(product_or_variant, currency)
33
+ def compare_at_price(product_or_variant, currency, context_options = {})
34
+ price = find_price(product_or_variant, currency, context_options)
28
35
  return nil if price.new_record? || price.compare_at_amount.blank?
29
36
 
30
37
  price.compare_at_amount
31
38
  end
32
39
 
33
- def display_compare_at_price(product_or_variant, currency)
34
- price = find_price(product_or_variant, currency)
40
+ def display_compare_at_price(product_or_variant, currency, context_options = {})
41
+ price = find_price(product_or_variant, currency, context_options)
35
42
  return nil if price.new_record? || price.compare_at_amount.blank?
36
43
 
37
44
  Spree::Money.new(price.compare_at_amount, currency: currency).to_s
38
45
  end
46
+
47
+ private
48
+
49
+ def build_pricing_context(variant, currency, options)
50
+ Spree::Pricing::Context.new(
51
+ variant: variant,
52
+ currency: currency,
53
+ store: options[:store],
54
+ zone: options[:tax_zone] || options[:zone],
55
+ user: options[:user],
56
+ quantity: options[:quantity]
57
+ )
58
+ end
39
59
  end
40
60
  end
41
61
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class WebhookDeliveryJob < Spree::BaseJob
5
+ queue_as Spree.queues.webhooks
6
+
7
+ retry_on StandardError, wait: :polynomially_longer, attempts: 5
8
+
9
+ def perform(delivery_id, secret_key)
10
+ delivery = Spree::WebhookDelivery.find_by(id: delivery_id)
11
+ return if delivery.nil?
12
+
13
+ Spree::Webhooks::DeliverWebhook.call(delivery: delivery, secret_key: secret_key)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,68 @@
1
+ require 'pagy'
2
+
3
+ module Spree
4
+ module Api
5
+ class Paginate
6
+ include Pagy::Method
7
+
8
+ def initialize(collection, params)
9
+ @collection = collection
10
+ @params = params
11
+
12
+ per_page_limit = Spree::Api::Config[:api_v2_per_page_limit]
13
+ @per_page = if params[:per_page].to_i.between?(1, per_page_limit)
14
+ params[:per_page].to_i
15
+ else
16
+ 25
17
+ end
18
+ end
19
+
20
+ def call
21
+ # Pass params as a hash-based request object for Pagy
22
+ # Pagy::Request accepts a hash with :params key
23
+ # Use to_unsafe_h for ActionController::Parameters, fallback to to_h for regular hashes
24
+ params_hash = @params.respond_to?(:to_unsafe_h) ? @params.to_unsafe_h : @params.to_h
25
+ request_hash = { params: params_hash }
26
+
27
+ # Uses countish paginator which is faster as it avoids COUNT queries
28
+ pagy, records = pagy(:countish, @collection, limit: @per_page, request: request_hash)
29
+
30
+ PagyCollection.new(records, pagy)
31
+ end
32
+ end
33
+
34
+ # Wrapper providing Kaminari-compatible interface for CollectionOptionsHelpers
35
+ # Includes Enumerable so jsonapi-serializer detects it as a collection
36
+ class PagyCollection < SimpleDelegator
37
+ include Enumerable
38
+
39
+ attr_reader :pagy
40
+
41
+ def initialize(records, pagy)
42
+ super(records)
43
+ @pagy = pagy
44
+ end
45
+
46
+ # Enumerable requires #each to be defined
47
+ def each(&block)
48
+ __getobj__.each(&block)
49
+ end
50
+
51
+ def next_page
52
+ @pagy.next
53
+ end
54
+
55
+ def prev_page
56
+ @pagy.previous
57
+ end
58
+
59
+ def total_pages
60
+ @pagy.pages
61
+ end
62
+
63
+ def total_count
64
+ @pagy.count
65
+ end
66
+ end
67
+ end
68
+ end
@@ -27,19 +27,27 @@ module Spree
27
27
  end
28
28
 
29
29
  attribute :price do |product, params|
30
- price(product, params[:currency])
30
+ price(product, params[:currency], pricing_context_options(params))
31
31
  end
32
32
 
33
33
  attribute :display_price do |product, params|
34
- display_price(product, params[:currency])
34
+ display_price(product, params[:currency], pricing_context_options(params))
35
35
  end
36
36
 
37
37
  attribute :compare_at_price do |product, params|
38
- compare_at_price(product, params[:currency])
38
+ compare_at_price(product, params[:currency], pricing_context_options(params))
39
39
  end
40
40
 
41
41
  attribute :display_compare_at_price do |product, params|
42
- display_compare_at_price(product, params[:currency])
42
+ display_compare_at_price(product, params[:currency], pricing_context_options(params))
43
+ end
44
+
45
+ def self.pricing_context_options(params)
46
+ {
47
+ store: params[:store],
48
+ user: params[:user],
49
+ tax_zone: params.dig(:price_options, :tax_zone)
50
+ }
43
51
  end
44
52
 
45
53
  belongs_to :tax_category, serializer: Spree.api.platform_tax_category_serializer
@@ -29,19 +29,27 @@ module Spree
29
29
  end
30
30
 
31
31
  attribute :price do |object, params|
32
- price(object, params[:currency])
32
+ price(object, params[:currency], pricing_context_options(params))
33
33
  end
34
34
 
35
35
  attribute :display_price do |object, params|
36
- display_price(object, params[:currency])
36
+ display_price(object, params[:currency], pricing_context_options(params))
37
37
  end
38
38
 
39
39
  attribute :compare_at_price do |object, params|
40
- compare_at_price(object, params[:currency])
40
+ compare_at_price(object, params[:currency], pricing_context_options(params))
41
41
  end
42
42
 
43
43
  attribute :display_compare_at_price do |object, params|
44
- display_compare_at_price(object, params[:currency])
44
+ display_compare_at_price(object, params[:currency], pricing_context_options(params))
45
+ end
46
+
47
+ def self.pricing_context_options(params)
48
+ {
49
+ store: params[:store],
50
+ user: params[:user],
51
+ tax_zone: params.dig(:price_options, :tax_zone)
52
+ }
45
53
  end
46
54
 
47
55
  belongs_to :product, serializer: Spree.api.platform_product_serializer
@@ -30,19 +30,27 @@ module Spree
30
30
  end
31
31
 
32
32
  attribute :price do |product, params|
33
- price(product, params[:currency])
33
+ price(product, params[:currency], pricing_context_options(params))
34
34
  end
35
35
 
36
36
  attribute :display_price do |product, params|
37
- display_price(product, params[:currency])
37
+ display_price(product, params[:currency], pricing_context_options(params))
38
38
  end
39
39
 
40
40
  attribute :compare_at_price do |product, params|
41
- compare_at_price(product, params[:currency])
41
+ compare_at_price(product, params[:currency], pricing_context_options(params))
42
42
  end
43
43
 
44
44
  attribute :display_compare_at_price do |product, params|
45
- display_compare_at_price(product, params[:currency])
45
+ display_compare_at_price(product, params[:currency], pricing_context_options(params))
46
+ end
47
+
48
+ def self.pricing_context_options(params)
49
+ {
50
+ store: params[:store],
51
+ user: params[:user],
52
+ tax_zone: params.dig(:price_options, :tax_zone)
53
+ }
46
54
  end
47
55
 
48
56
  attribute :localized_slugs do |product, params|
@@ -57,7 +65,7 @@ module Spree
57
65
  has_many :product_properties, serializer: Spree.api.storefront_product_property_serializer
58
66
 
59
67
  has_many :taxons, serializer: Spree.api.storefront_taxon_serializer, record_type: :taxon do |object, params|
60
- object.taxons_for_store(params[:store]).order(:id)
68
+ object.taxons_for_store(params[:store])
61
69
  end
62
70
 
63
71
  # all images from all variants
@@ -14,7 +14,7 @@ module Spree
14
14
  end
15
15
 
16
16
  attribute :has_products do |taxon|
17
- taxon.active_products_with_descendants.exists?
17
+ taxon.classification_count.positive? || taxon.active_products_with_descendants.exists?
18
18
  end
19
19
 
20
20
  attribute :header_url do |taxon|
@@ -26,19 +26,27 @@ module Spree
26
26
  end
27
27
 
28
28
  attribute :price do |product, params|
29
- price(product, params[:currency])
29
+ price(product, params[:currency], pricing_context_options(params))
30
30
  end
31
31
 
32
32
  attribute :display_price do |product, params|
33
- display_price(product, params[:currency])
33
+ display_price(product, params[:currency], pricing_context_options(params))
34
34
  end
35
35
 
36
36
  attribute :compare_at_price do |product, params|
37
- compare_at_price(product, params[:currency])
37
+ compare_at_price(product, params[:currency], pricing_context_options(params))
38
38
  end
39
39
 
40
40
  attribute :display_compare_at_price do |product, params|
41
- display_compare_at_price(product, params[:currency])
41
+ display_compare_at_price(product, params[:currency], pricing_context_options(params))
42
+ end
43
+
44
+ def self.pricing_context_options(params)
45
+ {
46
+ store: params[:store],
47
+ user: params[:user],
48
+ tax_zone: params.dig(:price_options, :tax_zone)
49
+ }
42
50
  end
43
51
 
44
52
  belongs_to :product, serializer: Spree.api.storefront_product_serializer
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+
6
+ module Spree
7
+ module Webhooks
8
+ class DeliverWebhook
9
+ TIMEOUT = 30
10
+
11
+ def self.call(delivery:, secret_key:)
12
+ new(delivery: delivery, secret_key: secret_key).call
13
+ end
14
+
15
+ def initialize(delivery:, secret_key:)
16
+ @delivery = delivery
17
+ @secret_key = secret_key
18
+ end
19
+
20
+ def call
21
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
+
23
+ response = make_request
24
+ execution_time = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
25
+
26
+ @delivery.complete!(
27
+ response_code: response.code.to_i,
28
+ execution_time: execution_time,
29
+ response_body: response.body.to_s.truncate(10_000)
30
+ )
31
+ rescue Net::OpenTimeout, Net::ReadTimeout => e
32
+ execution_time = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
33
+ Rails.error.report(e, context: { webhook_delivery_id: @delivery.id, url: @delivery.url })
34
+ @delivery.complete!(
35
+ execution_time: execution_time,
36
+ error_type: 'timeout',
37
+ request_errors: e.message
38
+ )
39
+ rescue StandardError => e
40
+ execution_time = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
41
+ Rails.error.report(e, context: { webhook_delivery_id: @delivery.id, url: @delivery.url })
42
+ @delivery.complete!(
43
+ execution_time: execution_time,
44
+ error_type: 'connection_error',
45
+ request_errors: e.message
46
+ )
47
+ end
48
+
49
+ private
50
+
51
+ def make_request
52
+ uri = URI.parse(@delivery.url)
53
+ http = Net::HTTP.new(uri.host, uri.port)
54
+ http.use_ssl = uri.scheme == 'https'
55
+ http.verify_mode = ssl_verify_mode
56
+ http.open_timeout = TIMEOUT
57
+ http.read_timeout = TIMEOUT
58
+
59
+ request = Net::HTTP::Post.new(uri.request_uri)
60
+ request['Content-Type'] = 'application/json'
61
+ request['User-Agent'] = 'Spree-Webhooks/1.0'
62
+ request['X-Spree-Webhook-Signature'] = generate_signature
63
+ request['X-Spree-Webhook-Event'] = @delivery.event_name
64
+ request.body = @delivery.payload.to_json
65
+
66
+ http.request(request)
67
+ end
68
+
69
+ def generate_signature
70
+ payload_json = @delivery.payload.to_json
71
+ OpenSSL::HMAC.hexdigest('SHA256', @secret_key, payload_json)
72
+ end
73
+
74
+ def ssl_verify_mode
75
+ if Spree::Api::Config.webhooks_verify_ssl
76
+ OpenSSL::SSL::VERIFY_PEER
77
+ else
78
+ OpenSSL::SSL::VERIFY_NONE
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Listens to Spree events and queues webhook deliveries for enabled endpoints.
5
+ #
6
+ # This subscriber listens to all Spree events and for each event, finds
7
+ # all enabled webhook endpoints that are subscribed to that event and queues
8
+ # a delivery job for each one.
9
+ #
10
+ # The event payload is passed through directly without transformation.
11
+ # Events should already be serialized by the EventSerializer.
12
+ #
13
+ # @example
14
+ # # Webhooks are automatically delivered when events are published
15
+ # Spree::Events.publish('order.completed', order: order)
16
+ #
17
+ class WebhookEventSubscriber < Spree::Subscriber
18
+ subscribes_to '*'
19
+
20
+ def handle(event)
21
+ return unless Spree::Api::Config.webhooks_enabled
22
+ return if event.store_id.blank?
23
+
24
+ # Find all active endpoints for this store subscribed to this event
25
+ endpoints = Spree::WebhookEndpoint.active.where(store_id: event.store_id).select { |endpoint| endpoint.subscribed_to?(event.name) }
26
+
27
+ return if endpoints.empty?
28
+
29
+ # Queue delivery for each endpoint
30
+ endpoints.each do |endpoint|
31
+ queue_delivery(endpoint, event)
32
+ end
33
+ rescue StandardError => e
34
+ Rails.logger.error "[Spree Webhooks] Error processing event: #{e.message}"
35
+ Rails.error.report(e)
36
+ end
37
+
38
+ private
39
+
40
+ def queue_delivery(endpoint, event)
41
+ # Build base payload (without delivery ID)
42
+ payload = build_payload(event)
43
+
44
+ # Create delivery record
45
+ delivery = endpoint.webhook_deliveries.create!(
46
+ event_name: event.name,
47
+ payload: payload
48
+ )
49
+
50
+ # Queue the delivery job
51
+ Spree::WebhookDeliveryJob.perform_later(delivery.id, endpoint.secret_key)
52
+ rescue StandardError => e
53
+ Rails.logger.error "[Spree Webhooks] Error queuing delivery for endpoint #{endpoint.id}: #{e.message}"
54
+ Rails.error.report(e)
55
+ end
56
+
57
+ def build_payload(event)
58
+ {
59
+ id: event.id,
60
+ name: event.name,
61
+ created_at: event.created_at.iso8601,
62
+ data: event.payload,
63
+ metadata: event.metadata
64
+ }
65
+ end
66
+ end
67
+ end
data/config/routes.rb CHANGED
@@ -206,8 +206,6 @@ Spree::Core::Engine.add_routes do
206
206
 
207
207
  # Webhooks API
208
208
  namespace :webhooks do
209
- resources :events, only: :index
210
- resources :subscribers
211
209
  end
212
210
 
213
211
  # Gift Cards API
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Api
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ class_option :migrate, type: :boolean, default: true
8
+
9
+ def add_migrations
10
+ run 'bundle exec rake railties:install:migrations FROM=spree_api'
11
+ end
12
+
13
+ def run_migrations
14
+ run_migrations = options[:migrate] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
15
+ if run_migrations
16
+ run 'bin/rails db:migrate'
17
+ else
18
+ puts 'Skipping rails db:migrate, don\'t forget to run it!'
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -8,6 +8,9 @@ module Spree
8
8
  preference :api_v2_collection_cache_namespace, :string, default: 'api_v2_collection_cache'
9
9
  preference :api_v2_content_type, :string, default: 'application/vnd.api+json'
10
10
  preference :api_v2_per_page_limit, :integer, default: 500
11
+
12
+ preference :webhooks_enabled, :boolean, default: true
13
+ preference :webhooks_verify_ssl, :boolean, default: !Rails.env.development?
11
14
  end
12
15
  end
13
16
  end
@@ -101,7 +101,7 @@ module Spree
101
101
  storefront_posts_sorter: -> { Spree::Dependencies.posts_sorter },
102
102
 
103
103
  # paginators
104
- storefront_collection_paginator: -> { Spree::Dependencies.collection_paginator },
104
+ storefront_collection_paginator: 'Spree::Api::Paginate',
105
105
 
106
106
  # finders
107
107
  storefront_address_finder: -> { Spree::Dependencies.address_finder },
@@ -1,4 +1,5 @@
1
1
  require 'rails/engine'
2
+ require 'pagy'
2
3
 
3
4
  require_relative 'dependencies'
4
5
  require_relative 'configuration'
@@ -18,6 +19,11 @@ module Spree
18
19
  Migrations.new(config, engine_name).check unless Rails.env.test?
19
20
  end
20
21
 
22
+ # Add API event subscribers
23
+ config.after_initialize do
24
+ Spree.subscribers << Spree::WebhookEventSubscriber
25
+ end
26
+
21
27
  def self.root
22
28
  @root ||= Pathname.new(File.expand_path('../../..', __dir__))
23
29
  end