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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/spree/api/v2/product_list_includes.rb +10 -22
- data/app/controllers/concerns/spree/api/v2/storefront/order_concern.rb +1 -1
- data/app/controllers/spree/api/v2/platform/classifications_controller.rb +1 -1
- data/app/controllers/spree/api/v2/resource_controller.rb +8 -5
- data/app/controllers/spree/api/v2/storefront/products_controller.rb +1 -4
- data/app/helpers/spree/api/v2/display_money_helper.rb +30 -10
- data/app/jobs/spree/webhook_delivery_job.rb +16 -0
- data/app/paginators/spree/api/paginate.rb +68 -0
- data/app/serializers/spree/api/v2/platform/product_serializer.rb +12 -4
- data/app/serializers/spree/api/v2/platform/variant_serializer.rb +12 -4
- data/app/serializers/spree/v2/storefront/product_serializer.rb +13 -5
- data/app/serializers/spree/v2/storefront/taxon_serializer.rb +1 -1
- data/app/serializers/spree/v2/storefront/variant_serializer.rb +12 -4
- data/app/services/spree/webhooks/deliver_webhook.rb +83 -0
- data/app/subscribers/spree/webhook_event_subscriber.rb +67 -0
- data/config/routes.rb +0 -2
- data/lib/generators/spree/api/install/install_generator.rb +24 -0
- data/lib/spree/api/configuration.rb +3 -0
- data/lib/spree/api/dependencies.rb +1 -1
- data/lib/spree/api/engine.rb +6 -0
- data/lib/spree/api/testing_support/matchers/webhooks.rb +1 -67
- data/lib/spree/api/testing_support/spree_webhooks.rb +1 -9
- data/lib/spree/api.rb +3 -0
- data/lib/spree_api.rb +0 -2
- metadata +25 -33
- data/app/controllers/spree/api/v2/platform/webhooks/events_controller.rb +0 -25
- data/app/controllers/spree/api/v2/platform/webhooks/subscribers_controller.rb +0 -25
- data/app/jobs/spree/webhooks/subscribers/make_request_job.rb +0 -17
- data/app/models/concerns/spree/webhooks/has_webhooks.rb +0 -95
- data/app/models/spree/order/webhooks.rb +0 -39
- data/app/models/spree/payment/webhooks.rb +0 -23
- data/app/models/spree/product/webhooks.rb +0 -42
- data/app/models/spree/shipment/webhooks.rb +0 -19
- data/app/models/spree/stock_item/webhooks.rb +0 -40
- data/app/models/spree/stock_movement/webhooks.rb +0 -49
- data/app/models/spree/variant/webhooks.rb +0 -25
- data/app/models/spree/webhooks/base.rb +0 -11
- data/app/models/spree/webhooks/event.rb +0 -20
- data/app/models/spree/webhooks/event_signature.rb +0 -33
- data/app/models/spree/webhooks/subscriber.rb +0 -88
- data/app/serializers/spree/api/v2/platform/webhooks/event_serializer.rb +0 -15
- data/app/serializers/spree/api/v2/platform/webhooks/subscriber_serializer.rb +0 -13
- data/app/services/spree/webhooks/subscribers/handle_request.rb +0 -77
- data/app/services/spree/webhooks/subscribers/make_request.rb +0 -83
- data/app/services/spree/webhooks/subscribers/queue_requests.rb +0 -27
- data/db/migrate/20210902162826_create_spree_webhooks_tables.rb +0 -16
- data/db/migrate/20211025162826_create_spree_webhooks_events.rb +0 -14
- data/db/migrate/20221221122100_add_secret_key_to_spree_webhooks_subscribers.rb +0 -5
- data/db/migrate/20230116204600_backfill_secret_key_for_spree_webhooks_subscribers.rb +0 -5
- data/db/migrate/20230116205000_change_secret_key_to_non_null_column.rb +0 -5
- data/lib/spree/api/testing_support/factories/webhook_event_factory.rb +0 -27
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf006c6d540a502177ba141362ccc562beb80a4ab19e61894c6cd7d270f3071e
|
|
4
|
+
data.tar.gz: a36baaff83201059e4c4b96b3d97e73c7e6eb8e94644fdc74c67320c4eed9eee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
|
@@ -51,11 +51,14 @@ module Spree
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def collection
|
|
54
|
-
@collection ||=
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
@@ -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.
|
|
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])
|
|
68
|
+
object.taxons_for_store(params[:store])
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
# all images from all variants
|
|
@@ -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
|
@@ -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:
|
|
104
|
+
storefront_collection_paginator: 'Spree::Api::Paginate',
|
|
105
105
|
|
|
106
106
|
# finders
|
|
107
107
|
storefront_address_finder: -> { Spree::Dependencies.address_finder },
|
data/lib/spree/api/engine.rb
CHANGED
|
@@ -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
|