From 9a9ded8021c1082a9e884ba4d93ea0039ca7b0b5 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Fri, 19 Jan 2024 14:05:20 -0600 Subject: [PATCH 1/9] Add webhook id and api version to webhook handler Shopify recommends verifing you did not receive duplicate webhook The webhook_id is used for this Also pass the API version header Add additional method for new webhook handler --- docs/usage/webhooks.md | 44 ++++++- lib/shopify_api/webhooks/handler.rb | 21 ++- lib/shopify_api/webhooks/registry.rb | 11 +- lib/shopify_api/webhooks/request.rb | 10 ++ test/test_helpers/new_fake_webhook_handler.rb | 24 ++++ test/webhooks/registry_test.rb | 123 +++++++++++++++++- 6 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 test/test_helpers/new_fake_webhook_handler.rb diff --git a/docs/usage/webhooks.md b/docs/usage/webhooks.md index 203e23cb8..a2d8f2986 100644 --- a/docs/usage/webhooks.md +++ b/docs/usage/webhooks.md @@ -7,10 +7,42 @@ If using in the Rails framework, we highly recommend you use the [shopify_app](h ## Create a Webhook Handler +### New webhook handler as of Version 13.5.0 +If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::WebhookHandler` and implement the `handle_webhook` method which accepts the following named parameters: data: `Hash[Symbol, untyped]`. An example implementation is shown below: + +You will also have to define a method called `use_handle_webhook?` which returns a boolean. If this method returns `true` then the `handle_webhook` method will be called when a webhook is received. If this method returns `false` then the `handle` method will be called when a webhook is received. The `handle` method is the old way of handling webhooks and will be deprecated in a future version of the gem. When using this method you will not have access to the `webhook_id` or `api_version` of the webhook. + +`data` will have the following keys +- `:topic` - The topic of the webhook +- `:shop` - The shop domain of the webhook +- `:body` - The body of the webhook +- `:webhook_id` - The id of the webhook event to [avoid duplicates](https://shopify.dev/docs/apps/webhooks/best-practices#ignore-duplicates) +- `:api_version` - The api version of the webhook + +```ruby +module WebhookHandler + extend ShopifyAPI::Webhooks::Handler + + class << self + def handle_webhook(data) + puts "Received webhook! topic: #{data[:topic]} shop: #{data[:shop]} body: #{data[:body]} webhook_id: #{data[:webhook_id]} api_version: #{data[:api_version]" + end + + def use_handle_webhook? + true + end + end +end +``` + +**Note:** As of version 13.5.0 the `handle` method is still available to be used but will be removed in a future version of the gem. It is recommended that you use the `handle_webhook` method instead. + + +### Webhook Handler for versions 13.4.0 and prior If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::WebhookHandler` and implement the handle method which accepts the following named parameters: topic: `String`, shop: `String`, and body: `Hash[String, untyped]`. An example implementation is shown below: ```ruby -module WebhookHandler +module WebhookHandler extend ShopifyAPI::Webhooks::Handler class << self @@ -28,18 +60,18 @@ end The next step is to add all the webhooks you would like to subscribe to for any shop to the webhook registry. To do this you can call `ShopifyAPI::Webhooks::Registry.add_registration` for each webhook you would like to handle. `add_registration` accepts a topic string, a delivery_method symbol (currently supporting `:http`, `:event_bridge`, and `:pub_sub`), a webhook path (the relative path for an http webhook) and a handler. This only needs to be done once when the app is started and we recommend doing this at the same time that you setup `ShopifyAPI::Context`. An example is shown below to register an http webhook: ```ruby -registration = ShopifyAPI::Webhooks::Registry.add_registration(topic: "orders/create", +registration = ShopifyAPI::Webhooks::Registry.add_registration(topic: "orders/create", delivery_method: :http, handler: WebhookHandler, - path: 'callback/orders/create') + path: 'callback/orders/create') ``` If you are only interested in particular fields, you can optionally filter the data sent by Shopify by specifying the `fields` parameter. Note that you will still receive a webhook request from Shopify every time the resource is updated, but only the specified fields will be sent: ```ruby registration = ShopifyAPI::Webhooks::Registry.add_registration( - topic: "orders/create", - delivery_method: :http, - handler: WebhookHandler, + topic: "orders/create", + delivery_method: :http, + handler: WebhookHandler, path: 'callback/orders/create', fields: ["number","note"] # this can also be a single comma separated string ) diff --git a/lib/shopify_api/webhooks/handler.rb b/lib/shopify_api/webhooks/handler.rb index 2430a4a2e..eafd88c1a 100644 --- a/lib/shopify_api/webhooks/handler.rb +++ b/lib/shopify_api/webhooks/handler.rb @@ -4,12 +4,29 @@ module ShopifyAPI module Webhooks module Handler + include Kernel extend T::Sig extend T::Helpers - interface! + abstract! - sig { abstract.params(topic: String, shop: String, body: T::Hash[String, T.untyped]).void } + sig do + params(data: T::Hash[Symbol, T.untyped]).void + end + def handle_webhook(data) + if use_handle_webhook? + raise NotImplementedError, "You must implement the `handle_webhook` method in your webhook handler class." + end + end + + sig do + abstract.params(topic: String, shop: String, body: T::Hash[String, T.untyped]).void + end def handle(topic:, shop:, body:); end + + sig { returns(T::Boolean) } + def use_handle_webhook? + false + end end end end diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index b357730e8..9adbc7978 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -193,7 +193,16 @@ def process(request) raise Errors::NoWebhookHandler, "No webhook handler found for topic: #{request.topic}." end - handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) + if handler.use_handle_webhook? + handler.handle_webhook({ topic: request.topic, shop: request.shop, body: request.parsed_body, + webhook_id: request.webhook_id, api_version: request.api_version, }) + else + handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) + ShopifyAPI::Logger.warn(<<~WARNING) + DEPRECATED: Use ShopifyAPI::Webhooks::Handler#handle_webhook + instead of ShopifyAPI::Webhooks::Handler#handle + WARNING + end end private diff --git a/lib/shopify_api/webhooks/request.rb b/lib/shopify_api/webhooks/request.rb index c283def95..23e077353 100644 --- a/lib/shopify_api/webhooks/request.rb +++ b/lib/shopify_api/webhooks/request.rb @@ -22,6 +22,16 @@ def shop T.cast(@headers["x-shopify-shop-domain"], String) end + sig { returns(String) } + def api_version + T.cast(@headers["x-shopify-api-version"], String) + end + + sig { returns(String) } + def webhook_id + T.cast(@headers["x-shopify-webhook-id"], String) + end + sig { override.returns(String) } def to_signable_string @raw_body diff --git a/test/test_helpers/new_fake_webhook_handler.rb b/test/test_helpers/new_fake_webhook_handler.rb new file mode 100644 index 000000000..9732b364d --- /dev/null +++ b/test/test_helpers/new_fake_webhook_handler.rb @@ -0,0 +1,24 @@ +# typed: false +# frozen_string_literal: true + +module TestHelpers + class NewFakeWebhookHandler + include ShopifyAPI::Webhooks::Handler + + def initialize(handler) + @handler = handler + end + + def handle(topic:, shop:, body:) + @handler.call(topic, shop, body) + end + + def handle_webhook(data) + @handler.call(data) + end + + def use_handle_webhook? + true + end + end +end diff --git a/test/webhooks/registry_test.rb b/test/webhooks/registry_test.rb index bc78fc638..64034af5e 100644 --- a/test/webhooks/registry_test.rb +++ b/test/webhooks/registry_test.rb @@ -23,6 +23,8 @@ def setup "x-shopify-topic" => @topic, "x-shopify-hmac-sha256" => Base64.encode64(hmac), "x-shopify-shop-domain" => @shop, + "x-shopify-webhook-id" => "b1234-eefd-4c9e-9520-049845a02082", + "x-shopify-api-version" => "2024-01", } @webhook_request = ShopifyAPI::Webhooks::Request.new(raw_body: "{}", headers: @headers) @@ -40,7 +42,7 @@ def test_process handler_called = false handler = TestHelpers::FakeWebhookHandler.new( - lambda do |topic, shop, body| + lambda do |topic, shop, body,| assert_equal(@topic, topic) assert_equal(@shop, shop) assert_equal({}, body) @@ -57,6 +59,29 @@ def test_process assert(handler_called) end + def test_process_new_handler + handler_called = false + + handler = TestHelpers::NewFakeWebhookHandler.new( + lambda do |data| + assert_equal(@topic, data[:topic]) + assert_equal(@shop, data[:shop]) + assert_equal({}, data[:body]) + assert_equal("b1234-eefd-4c9e-9520-049845a02082", data[:webhook_id]) + assert_equal("2024-01", data[:api_version]) + handler_called = true + end, + ) + + ShopifyAPI::Webhooks::Registry.add_registration( + topic: @topic, path: "path", delivery_method: :http, handler: handler, + ) + + ShopifyAPI::Webhooks::Registry.process(@webhook_request) + + assert(handler_called) + end + def test_process_hmac_validation_fails headers = { "x-shopify-topic" => "some/topic", @@ -359,6 +384,77 @@ def do_registration_test(delivery_method, path, fields: nil, metafield_namespace assert_equal(queries[delivery_method][:register_update_response], update_registration_response.body) end + def do_registration_test_new_handler(delivery_method, path, fields: nil, metafield_namespaces: nil) + ShopifyAPI::Webhooks::Registry.clear + + check_query_body = { query: queries[delivery_method][:check_query], variables: nil } + + stub_request(:post, @url) + .with(body: JSON.dump(check_query_body)) + .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_empty_response]) }) + + add_query_type = if fields + :register_add_query_with_fields + elsif metafield_namespaces + :register_add_query_with_metafield_namespaces + else + :register_add_query + end + add_response_type = if fields + :register_add_with_fields_response + elsif metafield_namespaces + :register_add_with_metafield_namespaces_response + else + :register_add_response + end + + stub_request(:post, @url) + .with(body: JSON.dump({ query: queries[delivery_method][add_query_type], variables: nil })) + .to_return({ status: 200, body: JSON.dump(queries[delivery_method][add_response_type]) }) + + ShopifyAPI::Webhooks::Registry.add_registration( + topic: @topic, + delivery_method: delivery_method, + path: path, + handler: TestHelpers::NewFakeWebhookHandler.new( + lambda do |data| + end, + ), + fields: fields, + metafield_namespaces: metafield_namespaces, + ) + registration_response = ShopifyAPI::Webhooks::Registry.register_all( + session: @session, + )[0] + + assert(registration_response.success) + assert_equal(queries[delivery_method][add_response_type], registration_response.body) + + stub_request(:post, @url) + .with(body: JSON.dump(check_query_body)) + .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_existing_response]) }) + + stub_request(:post, @url) + .with(body: JSON.dump({ query: queries[delivery_method][:register_update_query], variables: nil })) + .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:register_update_response]) }) + + ShopifyAPI::Webhooks::Registry.add_registration( + topic: @topic, + delivery_method: delivery_method, + path: "#{path}-updated", + handler: TestHelpers::NewFakeWebhookHandler.new( + lambda do |data| + end, + ), + ) + update_registration_response = ShopifyAPI::Webhooks::Registry.register_all( + session: @session, + )[0] + + assert(update_registration_response.success) + assert_equal(queries[delivery_method][:register_update_response], update_registration_response.body) + end + def do_registration_check_error_test(delivery_method, path) ShopifyAPI::Webhooks::Registry.clear body = { query: queries[delivery_method][:check_query], variables: nil } @@ -383,6 +479,31 @@ def do_registration_check_error_test(delivery_method, path) ) end end + + def do_registration_check_error_test_new_handler(delivery_method, path) + ShopifyAPI::Webhooks::Registry.clear + body = { query: queries[delivery_method][:check_query], variables: nil } + + stub_request(:post, @url) + .with(body: JSON.dump(body)) + .to_return(status: 304) + + ShopifyAPI::Webhooks::Registry.add_registration( + topic: @topic, + delivery_method: delivery_method, + path: path, + handler: TestHelpers::NewFakeWebhookHandler.new( + lambda do |data| + end, + ), + ) + + assert_raises(StandardError) do + ShopifyAPI::Webhooks::Registry.register_all( + session: @session, + ) + end + end end end end From 4ce783da2cf1ca1d9dffbd4569f641b7fcabdd5c Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Wed, 24 Jan 2024 10:31:26 -0600 Subject: [PATCH 2/9] Change test name --- test/webhooks/registry_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/webhooks/registry_test.rb b/test/webhooks/registry_test.rb index 64034af5e..7e4b3de08 100644 --- a/test/webhooks/registry_test.rb +++ b/test/webhooks/registry_test.rb @@ -384,7 +384,7 @@ def do_registration_test(delivery_method, path, fields: nil, metafield_namespace assert_equal(queries[delivery_method][:register_update_response], update_registration_response.body) end - def do_registration_test_new_handler(delivery_method, path, fields: nil, metafield_namespaces: nil) + def do_registration_new_handler_test(delivery_method, path, fields: nil, metafield_namespaces: nil) ShopifyAPI::Webhooks::Registry.clear check_query_body = { query: queries[delivery_method][:check_query], variables: nil } From 5a9436a9fe7efe5ced12ca6ef757a0dd651f7062 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Wed, 24 Jan 2024 10:32:34 -0600 Subject: [PATCH 3/9] Add link to docs in deprecation message --- lib/shopify_api/webhooks/registry.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index 9adbc7978..14ae662e5 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -200,7 +200,8 @@ def process(request) handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) ShopifyAPI::Logger.warn(<<~WARNING) DEPRECATED: Use ShopifyAPI::Webhooks::Handler#handle_webhook - instead of ShopifyAPI::Webhooks::Handler#handle + instead of ShopifyAPI::Webhooks::Handler#handle. + https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler WARNING end end From 99d3ceb3f9f4b694d7bf78c0cad5aede2518553c Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Wed, 24 Jan 2024 13:02:18 -0600 Subject: [PATCH 4/9] Refactor to use multiple handler modules --- docs/usage/webhooks.md | 29 +++++++---------- lib/shopify_api/webhooks/handler.rb | 32 +++++++++++-------- lib/shopify_api/webhooks/registration.rb | 4 +-- lib/shopify_api/webhooks/registry.rb | 10 +++--- test/test_helpers/new_fake_webhook_handler.rb | 12 ++----- test/webhooks/registry_test.rb | 10 +++--- 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/docs/usage/webhooks.md b/docs/usage/webhooks.md index a2d8f2986..df9fa6261 100644 --- a/docs/usage/webhooks.md +++ b/docs/usage/webhooks.md @@ -7,17 +7,14 @@ If using in the Rails framework, we highly recommend you use the [shopify_app](h ## Create a Webhook Handler -### New webhook handler as of Version 13.5.0 -If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::WebhookHandler` and implement the `handle_webhook` method which accepts the following named parameters: data: `Hash[Symbol, untyped]`. An example implementation is shown below: - -You will also have to define a method called `use_handle_webhook?` which returns a boolean. If this method returns `true` then the `handle_webhook` method will be called when a webhook is received. If this method returns `false` then the `handle` method will be called when a webhook is received. The `handle` method is the old way of handling webhooks and will be deprecated in a future version of the gem. When using this method you will not have access to the `webhook_id` or `api_version` of the webhook. +If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::WebhookHandler` and implement the `handle` method which accepts the following named parameters: data: `WebhookMetadata`. An example implementation is shown below: `data` will have the following keys -- `:topic` - The topic of the webhook -- `:shop` - The shop domain of the webhook -- `:body` - The body of the webhook -- `:webhook_id` - The id of the webhook event to [avoid duplicates](https://shopify.dev/docs/apps/webhooks/best-practices#ignore-duplicates) -- `:api_version` - The api version of the webhook +- `topic`, `String` - The topic of the webhook +- `shop`, `String` - The shop domain of the webhook +- `body`, `T::Hash[String, T.untyped]`- The body of the webhook +- `webhook_id`, `String` - The id of the webhook event to [avoid duplicates](https://shopify.dev/docs/apps/webhooks/best-practices#ignore-duplicates) +- `api_version`, `String` - The api version of the webhook ```ruby module WebhookHandler @@ -25,21 +22,19 @@ module WebhookHandler class << self def handle_webhook(data) - puts "Received webhook! topic: #{data[:topic]} shop: #{data[:shop]} body: #{data[:body]} webhook_id: #{data[:webhook_id]} api_version: #{data[:api_version]" - end - - def use_handle_webhook? - true + puts "Received webhook! topic: #{data.topic} shop: #{data.shop} body: #{data.body} webhook_id: #{data.webhook_id} api_version: #{data.api_version" end end end ``` -**Note:** As of version 13.5.0 the `handle` method is still available to be used but will be removed in a future version of the gem. It is recommended that you use the `handle_webhook` method instead. +**Note:** As of version 13.5.0 the `ShopifyAPI::Webhooks::Handler` class is still available to be used but will be removed in a future version of the gem. +### Best Practices +It is recommended that in order to respond quickly to the Shopify webhook request that the handler not do any heavy logic or network calls, rather it should simply enqueue the work in some job queue in order to be executed later. ### Webhook Handler for versions 13.4.0 and prior -If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::WebhookHandler` and implement the handle method which accepts the following named parameters: topic: `String`, shop: `String`, and body: `Hash[String, untyped]`. An example implementation is shown below: +If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::Handler` and implement the handle method which accepts the following named parameters: topic: `String`, shop: `String`, and body: `Hash[String, untyped]`. An example implementation is shown below: ```ruby module WebhookHandler @@ -53,8 +48,6 @@ module WebhookHandler end ``` -**Note:** It is recommended that in order to respond quickly to the Shopify webhook request that the handler not do any heavy logic or network calls, rather it should simply enqueue the work in some job queue in order to be executed later. - ## Add to Webhook Registry The next step is to add all the webhooks you would like to subscribe to for any shop to the webhook registry. To do this you can call `ShopifyAPI::Webhooks::Registry.add_registration` for each webhook you would like to handle. `add_registration` accepts a topic string, a delivery_method symbol (currently supporting `:http`, `:event_bridge`, and `:pub_sub`), a webhook path (the relative path for an http webhook) and a handler. This only needs to be done once when the app is started and we recommend doing this at the same time that you setup `ShopifyAPI::Context`. An example is shown below to register an http webhook: diff --git a/lib/shopify_api/webhooks/handler.rb b/lib/shopify_api/webhooks/handler.rb index eafd88c1a..22a4de96c 100644 --- a/lib/shopify_api/webhooks/handler.rb +++ b/lib/shopify_api/webhooks/handler.rb @@ -3,30 +3,36 @@ module ShopifyAPI module Webhooks + class WebhookMetadata < T::Struct + const :topic, String + const :shop, String + const :body, T::Hash[String, T.untyped] + const :api_version, String + const :webhook_id, String + end + module Handler include Kernel extend T::Sig extend T::Helpers - abstract! - - sig do - params(data: T::Hash[Symbol, T.untyped]).void - end - def handle_webhook(data) - if use_handle_webhook? - raise NotImplementedError, "You must implement the `handle_webhook` method in your webhook handler class." - end - end + interface! sig do abstract.params(topic: String, shop: String, body: T::Hash[String, T.untyped]).void end def handle(topic:, shop:, body:); end + end + + module WebhookHandler + include Kernel + extend T::Sig + extend T::Helpers + interface! - sig { returns(T::Boolean) } - def use_handle_webhook? - false + sig do + abstract.params(data: WebhookMetadata).void end + def handle(data:); end end end end diff --git a/lib/shopify_api/webhooks/registration.rb b/lib/shopify_api/webhooks/registration.rb index c8a2e6c91..eb786b70e 100644 --- a/lib/shopify_api/webhooks/registration.rb +++ b/lib/shopify_api/webhooks/registration.rb @@ -13,7 +13,7 @@ class Registration sig { returns(String) } attr_reader :topic - sig { returns(T.nilable(Handler)) } + sig { returns(T.nilable(T.any(Handler, WebhookHandler))) } attr_reader :handler sig { returns(T.nilable(T::Array[String])) } @@ -23,7 +23,7 @@ class Registration attr_reader :metafield_namespaces sig do - params(topic: String, path: String, handler: T.nilable(Handler), + params(topic: String, path: String, handler: T.nilable(T.any(Handler, WebhookHandler)), fields: T.nilable(T.any(String, T::Array[String])), metafield_namespaces: T.nilable(T::Array[String])).void end diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index 14ae662e5..538dd7892 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -17,7 +17,7 @@ class << self params(topic: String, delivery_method: Symbol, path: String, - handler: T.nilable(Handler), + handler: T.nilable(T.any(Handler, WebhookHandler)), fields: T.nilable(T.any(String, T::Array[String])), metafield_namespaces: T.nilable(T::Array[String])).void end @@ -193,13 +193,13 @@ def process(request) raise Errors::NoWebhookHandler, "No webhook handler found for topic: #{request.topic}." end - if handler.use_handle_webhook? - handler.handle_webhook({ topic: request.topic, shop: request.shop, body: request.parsed_body, - webhook_id: request.webhook_id, api_version: request.api_version, }) + if handler.is_a?(WebhookHandler) + handler.handle(data: WebhookMetadata.new(topic: request.topic, shop: request.shop, + body: request.parsed_body, api_version: request.api_version, webhook_id: request.webhook_id)) else handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) ShopifyAPI::Logger.warn(<<~WARNING) - DEPRECATED: Use ShopifyAPI::Webhooks::Handler#handle_webhook + DEPRECATED: Use ShopifyAPI::Webhooks::WebhookHandler#handle instead of ShopifyAPI::Webhooks::Handler#handle. https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler WARNING diff --git a/test/test_helpers/new_fake_webhook_handler.rb b/test/test_helpers/new_fake_webhook_handler.rb index 9732b364d..7de1ddd72 100644 --- a/test/test_helpers/new_fake_webhook_handler.rb +++ b/test/test_helpers/new_fake_webhook_handler.rb @@ -3,22 +3,14 @@ module TestHelpers class NewFakeWebhookHandler - include ShopifyAPI::Webhooks::Handler + include ShopifyAPI::Webhooks::WebhookHandler def initialize(handler) @handler = handler end - def handle(topic:, shop:, body:) - @handler.call(topic, shop, body) - end - - def handle_webhook(data) + def handle(data:) @handler.call(data) end - - def use_handle_webhook? - true - end end end diff --git a/test/webhooks/registry_test.rb b/test/webhooks/registry_test.rb index 7e4b3de08..37bac2095 100644 --- a/test/webhooks/registry_test.rb +++ b/test/webhooks/registry_test.rb @@ -64,11 +64,11 @@ def test_process_new_handler handler = TestHelpers::NewFakeWebhookHandler.new( lambda do |data| - assert_equal(@topic, data[:topic]) - assert_equal(@shop, data[:shop]) - assert_equal({}, data[:body]) - assert_equal("b1234-eefd-4c9e-9520-049845a02082", data[:webhook_id]) - assert_equal("2024-01", data[:api_version]) + assert_equal(@topic, data.topic) + assert_equal(@shop, data.shop) + assert_equal({}, data.body) + assert_equal("b1234-eefd-4c9e-9520-049845a02082", data.webhook_id) + assert_equal("2024-01", data.api_version) handler_called = true end, ) From aaa46e112f21139d885858234198b0849f5277c5 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Wed, 24 Jan 2024 14:33:01 -0600 Subject: [PATCH 5/9] Add logger deprecated --- lib/shopify_api/webhooks/registry.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index 538dd7892..9a97f501b 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -198,11 +198,12 @@ def process(request) body: request.parsed_body, api_version: request.api_version, webhook_id: request.webhook_id)) else handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) - ShopifyAPI::Logger.warn(<<~WARNING) - DEPRECATED: Use ShopifyAPI::Webhooks::WebhookHandler#handle + ShopifyAPI::Logger.deprecated( + "DEPRECATED: Use ShopifyAPI::Webhooks::WebhookHandler#handle \ instead of ShopifyAPI::Webhooks::Handler#handle. - https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler - WARNING + https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler", + "14.0.0", + ) end end From 99015071134a3aa553c260cc832cbd9ea607e627 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Thu, 25 Jan 2024 14:55:42 -0600 Subject: [PATCH 6/9] Change back to warn --- lib/shopify_api/webhooks/registry.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index 9a97f501b..09be6b7fc 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -198,12 +198,11 @@ def process(request) body: request.parsed_body, api_version: request.api_version, webhook_id: request.webhook_id)) else handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) - ShopifyAPI::Logger.deprecated( + ShopifyAPI::Logger.warn(<<~WARNING) "DEPRECATED: Use ShopifyAPI::Webhooks::WebhookHandler#handle \ instead of ShopifyAPI::Webhooks::Handler#handle. - https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler", - "14.0.0", - ) + https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler" + WARNING end end From f2a3eeec1b55aa2985dc09a225b59f7e3a083be9 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Thu, 25 Jan 2024 15:02:27 -0600 Subject: [PATCH 7/9] Revert "Change back to warn" This reverts commit 99015071134a3aa553c260cc832cbd9ea607e627. --- lib/shopify_api/webhooks/registry.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index 09be6b7fc..9a97f501b 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -198,11 +198,12 @@ def process(request) body: request.parsed_body, api_version: request.api_version, webhook_id: request.webhook_id)) else handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body) - ShopifyAPI::Logger.warn(<<~WARNING) + ShopifyAPI::Logger.deprecated( "DEPRECATED: Use ShopifyAPI::Webhooks::WebhookHandler#handle \ instead of ShopifyAPI::Webhooks::Handler#handle. - https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler" - WARNING + https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler", + "14.0.0", + ) end end From 39163f00ab44ceedf86f27aefbff57d85f5e1d16 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Fri, 26 Jan 2024 09:17:18 -0600 Subject: [PATCH 8/9] Require handler in fake handlers the pipeline started failing without these --- test/test_helpers/fake_webhook_handler.rb | 2 ++ test/test_helpers/new_fake_webhook_handler.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/test/test_helpers/fake_webhook_handler.rb b/test/test_helpers/fake_webhook_handler.rb index 766b6eb41..f69ef6018 100644 --- a/test/test_helpers/fake_webhook_handler.rb +++ b/test/test_helpers/fake_webhook_handler.rb @@ -1,6 +1,8 @@ # typed: false # frozen_string_literal: true +require_relative "../../lib/shopify_api/webhooks/handler" + module TestHelpers class FakeWebhookHandler include ShopifyAPI::Webhooks::Handler diff --git a/test/test_helpers/new_fake_webhook_handler.rb b/test/test_helpers/new_fake_webhook_handler.rb index 7de1ddd72..6a9894896 100644 --- a/test/test_helpers/new_fake_webhook_handler.rb +++ b/test/test_helpers/new_fake_webhook_handler.rb @@ -1,6 +1,7 @@ # typed: false # frozen_string_literal: true +require_relative "../../lib/shopify_api/webhooks/handler" module TestHelpers class NewFakeWebhookHandler include ShopifyAPI::Webhooks::WebhookHandler From 0aa3f90d157cf78c472380fdea17a7a989c538ef Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Fri, 26 Jan 2024 09:31:58 -0600 Subject: [PATCH 9/9] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d50f3f4..ce6dac467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api ## Unreleased - [#1254](https://github.com/Shopify/shopify-api-ruby/pull/1254) Introduce token exchange API for fetching access tokens. This feature is currently unstable and cannot be used yet. +- [#1268](https://github.com/Shopify/shopify-api-ruby/pull/1268) Add [new webhook handler interface](https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler) to provide `webhook_id ` and `api_version` information to webhook handlers. ## 13.4.0 - [#1210](https://github.com/Shopify/shopify-api-ruby/pull/1246) Add context option `response_as_struct` to allow GraphQL API responses to be accessed via dot notation.