shopify_api 14.6.0 → 14.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1adedcbd54d0a4bff1dfeef88fc867ddf3813f6f619c8f4ad75345a9669c468b
4
- data.tar.gz: 2b5b7115e1a9d469cb62b23f18a9ff41f45216a6e4d76ad07dcd110460271391
3
+ metadata.gz: 2594b938c059a24c4f17f5a435612a01fe880eb4ab5096f25ac0d0513129c71c
4
+ data.tar.gz: 3b584fffb9213fefaf5f1a603c76de8292ecb30e7434ccc63af7265ab3377987
5
5
  SHA512:
6
- metadata.gz: 2ee3b4ff5f00993b64a5dc89c3618da8c757ae3451d34c298b6da01ea4659d38c3c56f70ca066662059147b67e23c0816539ca9d5e2526c270a28e91c903b166
7
- data.tar.gz: 3018dc46d85e99cff6821d8057b8a61cb59adeb41f856db18a0ecab0e47f17aecc86ffeb23c45a722121988c97f6bb262d6913a88d2bc1a5fdc572fb7b0fca05
6
+ metadata.gz: 5e565a15428752c7838332f42d02c6aad73fe837dcc1c91774ed2cd523c81b6cd440e82dc0f2d8d65494a0d7715bb75f8533a3105304d2a510b1cd104690b1f2
7
+ data.tar.gz: e3625bc9e1e17ef36162fcc8c416478dbdf9f8b7bc868fb228d07ba4c2878bedb72738765e82737da9cd59b4c5d7197ba81c15d9013a38f13987e5ba3fbdf93d
data/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  Note: For changes to the API, see https://shopify.dev/changelog?filter=api
4
4
  ## Unreleased
5
5
 
6
+ ## 14.7.0
7
+
8
+ - [#1347](https://github.com/Shopify/shopify-api-ruby/pull/1347) Extend webhook registration to support filters
9
+ - [#1344](https://github.com/Shopify/shopify-api-ruby/pull/1344) Allow ShopifyAPI::Webhooks::Registry to update a webhook when fields or metafield_namespaces are changed.
10
+ - [#1343](https://github.com/Shopify/shopify-api-ruby/pull/1343) Make ShopifyAPI::Context::scope parameter optional. `scope` defaults to empty list `[]`.
11
+
6
12
  ## 14.6.0
7
13
 
8
14
  - [#1337](https://github.com/Shopify/shopify-api-ruby/pull/1337) Fix type for Shop#google_apps_login_enabled
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (14.6.0)
4
+ shopify_api (14.7.0)
5
5
  activesupport
6
6
  concurrent-ruby
7
7
  hash_diff
@@ -16,20 +16,24 @@ PATH
16
16
  GEM
17
17
  remote: https://rubygems.org/
18
18
  specs:
19
- activesupport (7.1.4)
19
+ activesupport (7.1.5)
20
20
  base64
21
+ benchmark (>= 0.3)
21
22
  bigdecimal
22
23
  concurrent-ruby (~> 1.0, >= 1.0.2)
23
24
  connection_pool (>= 2.2.5)
24
25
  drb
25
26
  i18n (>= 1.6, < 2)
27
+ logger (>= 1.4.2)
26
28
  minitest (>= 5.1)
27
29
  mutex_m
30
+ securerandom (>= 0.3)
28
31
  tzinfo (~> 2.0)
29
32
  addressable (2.8.0)
30
33
  public_suffix (>= 2.0.2, < 5.0)
31
34
  ast (2.4.2)
32
35
  base64 (0.2.0)
36
+ benchmark (0.4.0)
33
37
  bigdecimal (3.1.8)
34
38
  byebug (11.1.3)
35
39
  coderay (1.1.3)
@@ -50,21 +54,22 @@ GEM
50
54
  i18n (1.14.6)
51
55
  concurrent-ruby (~> 1.0)
52
56
  json (2.7.1)
53
- jwt (2.9.1)
57
+ jwt (2.9.3)
54
58
  base64
55
59
  language_server-protocol (3.17.0.3)
60
+ logger (1.6.1)
56
61
  method_source (1.0.0)
57
62
  mini_mime (1.1.5)
58
63
  minitest (5.15.0)
59
64
  mocha (1.13.0)
60
65
  multi_xml (0.6.0)
61
- mutex_m (0.2.0)
66
+ mutex_m (0.3.0)
62
67
  netrc (0.11.0)
63
- oj (3.16.6)
68
+ oj (3.16.7)
64
69
  bigdecimal (>= 3.0)
65
70
  ostruct (>= 0.2)
66
71
  openssl (3.2.0)
67
- ostruct (0.6.0)
72
+ ostruct (0.6.1)
68
73
  parallel (1.24.0)
69
74
  parser (3.3.0.5)
70
75
  ast (~> 2.4.1)
@@ -104,7 +109,7 @@ GEM
104
109
  rubocop-sorbet (0.6.11)
105
110
  rubocop (>= 0.90.0)
106
111
  ruby-progressbar (1.13.0)
107
- securerandom (0.3.1)
112
+ securerandom (0.3.2)
108
113
  sorbet (0.5.11230)
109
114
  sorbet-static (= 0.5.11230)
110
115
  sorbet-runtime (0.5.11230)
data/docs/usage/oauth.md CHANGED
@@ -123,6 +123,19 @@ class ShopifyCallbackController < ApplicationController
123
123
  ```
124
124
 
125
125
  #### 3. Begin OAuth
126
+ To request access scopes from the shop during authorization code grant OAuth flow,
127
+ configure access scopes needed by adding the `scope` parameter to the `ShopifyAPI::Context.setup` method in your configuration.
128
+
129
+ ```ruby
130
+ ShopifyAPI::Context.setup(
131
+ api_key: <SHOPIFY_API_KEY>,
132
+ api_secret_key: <SHOPIFY_API_SECRET>,
133
+ api_version: <SHOPIFY_API_VERSION>,
134
+ scope: <SHOPIFY_API_SCOPES>, # Accepts array or string: "read_orders, write_products" or ["read_orders", "write_products"]
135
+ ...
136
+ )
137
+ ```
138
+
126
139
  Use [`ShopifyAPI::Auth::Oauth.begin_auth`](https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/auth/oauth.rb#L22) method to start OAuth process for your app.
127
140
 
128
141
  #### Input
@@ -131,6 +144,7 @@ Use [`ShopifyAPI::Auth::Oauth.begin_auth`](https://github.com/Shopify/shopify-ap
131
144
  | `shop` | `String` | Yes | - | A Shopify domain name in the form `{exampleshop}.myshopify.com`. |
132
145
  | `redirect_path` | `String` | Yes | - | The redirect path used for callback with a leading `/`. The route should be allowed under the app settings. |
133
146
  | `is_online` | `Boolean` | No | `true` | `true` if the session is online and `false` otherwise. |
147
+ | `scope_override`| `String` or `[String]` | No | `nil` | `nil` will request access scopes configured in `ShopifyAPI::Context.setup` during OAuth flow. Modify this to override the access scopes being requested. Accepts array or string: "read_orders, write_products" or ["read_orders", "write_products"]. |
134
148
 
135
149
  #### Output
136
150
  `begin_auth` method will return a hash result in the form of:
@@ -82,6 +82,17 @@ registration = ShopifyAPI::Webhooks::Registry.add_registration(
82
82
  )
83
83
  ```
84
84
 
85
+ If you need to filter the webhooks you want to receive, you can use a [webhooks filter](https://shopify.dev/docs/apps/build/webhooks/customize/filters), which can be specified on registration through the `filter` parameter.
86
+
87
+ ```ruby
88
+ registration = ShopifyAPI::Webhooks::Registry.add_registration(
89
+ topic: "products/update",
90
+ delivery_method: :http,
91
+ handler: WebhookHandler,
92
+ filter: "variants.price:>=10.00"
93
+ )
94
+ ```
95
+
85
96
  **Note**: The webhooks you register with Shopify are saved in the Shopify platform, but the local `ShopifyAPI::Webhooks::Registry` needs to be reloaded whenever your server restarts.
86
97
 
87
98
  ### EventBridge and PubSub Webhooks
@@ -33,9 +33,9 @@ module ShopifyAPI
33
33
  api_key: String,
34
34
  api_secret_key: String,
35
35
  api_version: String,
36
- scope: T.any(T::Array[String], String),
37
36
  is_private: T::Boolean,
38
37
  is_embedded: T::Boolean,
38
+ scope: T.any(T::Array[String], String),
39
39
  log_level: T.any(String, Symbol),
40
40
  logger: T.untyped,
41
41
  host_name: T.nilable(String),
@@ -51,9 +51,9 @@ module ShopifyAPI
51
51
  api_key:,
52
52
  api_secret_key:,
53
53
  api_version:,
54
- scope:,
55
54
  is_private:,
56
55
  is_embedded:,
56
+ scope: [],
57
57
  log_level: :info,
58
58
  logger: ::Logger.new($stdout),
59
59
  host_name: nil,
@@ -23,6 +23,7 @@ module ShopifyAPI
23
23
  @api_version = T.let(nil, T.nilable(String))
24
24
  @created_at = T.let(nil, T.nilable(String))
25
25
  @fields = T.let(nil, T.nilable(T::Array[T.untyped]))
26
+ @filter = T.let(nil, T.nilable(String))
26
27
  @format = T.let(nil, T.nilable(String))
27
28
  @id = T.let(nil, T.nilable(Integer))
28
29
  @metafield_namespaces = T.let(nil, T.nilable(T::Array[T.untyped]))
@@ -23,6 +23,7 @@ module ShopifyAPI
23
23
  @api_version = T.let(nil, T.nilable(String))
24
24
  @created_at = T.let(nil, T.nilable(String))
25
25
  @fields = T.let(nil, T.nilable(T::Array[T.untyped]))
26
+ @filter = T.let(nil, T.nilable(String))
26
27
  @format = T.let(nil, T.nilable(String))
27
28
  @id = T.let(nil, T.nilable(Integer))
28
29
  @metafield_namespaces = T.let(nil, T.nilable(T::Array[T.untyped]))
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ShopifyAPI
5
- VERSION = "14.6.0"
5
+ VERSION = "14.7.0"
6
6
  end
@@ -22,18 +22,23 @@ module ShopifyAPI
22
22
  sig { returns(T.nilable(T::Array[String])) }
23
23
  attr_reader :metafield_namespaces
24
24
 
25
+ sig { returns(T.nilable(String)) }
26
+ attr_reader :filter
27
+
25
28
  sig do
26
29
  params(topic: String, path: String, handler: T.nilable(T.any(Handler, WebhookHandler)),
27
30
  fields: T.nilable(T.any(String, T::Array[String])),
28
- metafield_namespaces: T.nilable(T::Array[String])).void
31
+ metafield_namespaces: T.nilable(T::Array[String]),
32
+ filter: T.nilable(String)).void
29
33
  end
30
- def initialize(topic:, path:, handler: nil, fields: nil, metafield_namespaces: nil)
34
+ def initialize(topic:, path:, handler: nil, fields: nil, metafield_namespaces: nil, filter: nil)
31
35
  @topic = T.let(topic.gsub("/", "_").upcase, String)
32
36
  @path = path
33
37
  @handler = handler
34
38
  fields_array = fields.is_a?(String) ? fields.split(FIELDS_DELIMITER) : fields
35
39
  @fields = T.let(fields_array&.map(&:strip)&.compact, T.nilable(T::Array[String]))
36
40
  @metafield_namespaces = T.let(metafield_namespaces&.map(&:strip)&.compact, T.nilable(T::Array[String]))
41
+ @filter = filter
37
42
  end
38
43
 
39
44
  sig { abstract.returns(String) }
@@ -48,7 +53,15 @@ module ShopifyAPI
48
53
  sig { abstract.returns(String) }
49
54
  def build_check_query; end
50
55
 
51
- sig { abstract.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) }
56
+ sig do
57
+ abstract.params(body: T::Hash[String, T.untyped]).returns({
58
+ webhook_id: T.nilable(String),
59
+ current_address: T.nilable(String),
60
+ fields: T::Array[String],
61
+ metafield_namespaces: T::Array[String],
62
+ filter: T.nilable(String),
63
+ })
64
+ end
52
65
  def parse_check_result(body); end
53
66
 
54
67
  sig { params(webhook_id: T.nilable(String)).returns(String) }
@@ -81,6 +94,7 @@ module ShopifyAPI
81
94
  attributes = ["id"]
82
95
  attributes << "includeFields" if @fields
83
96
  attributes << "metafieldNamespaces" if @metafield_namespaces
97
+ attributes << "filter" if @filter
84
98
  attributes
85
99
  end
86
100
  end
@@ -14,7 +14,8 @@ module ShopifyAPI
14
14
 
15
15
  sig { override.returns(T::Hash[Symbol, String]) }
16
16
  def subscription_args
17
- { arn: callback_address, includeFields: fields, metafieldNamespaces: metafield_namespaces }.compact
17
+ { arn: callback_address, includeFields: fields,
18
+ metafieldNamespaces: metafield_namespaces, filter: filter, }.compact
18
19
  end
19
20
 
20
21
  sig { override.params(webhook_id: T.nilable(String)).returns(String) }
@@ -30,6 +31,9 @@ module ShopifyAPI
30
31
  edges {
31
32
  node {
32
33
  id
34
+ includeFields
35
+ metafieldNamespaces
36
+ filter
33
37
  endpoint {
34
38
  __typename
35
39
  ... on WebhookEventBridgeEndpoint {
@@ -43,17 +47,32 @@ module ShopifyAPI
43
47
  QUERY
44
48
  end
45
49
 
46
- sig { override.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) }
50
+ sig do
51
+ override.params(body: T::Hash[String, T.untyped]).returns({
52
+ webhook_id: T.nilable(String),
53
+ current_address: T.nilable(String),
54
+ fields: T::Array[String],
55
+ metafield_namespaces: T::Array[String],
56
+ filter: T.nilable(String),
57
+ })
58
+ end
47
59
  def parse_check_result(body)
48
60
  edges = body.dig("data", "webhookSubscriptions", "edges") || {}
49
61
  webhook_id = nil
62
+ fields = []
63
+ metafield_namespaces = []
64
+ filter = nil
50
65
  current_address = nil
51
66
  unless edges.empty?
52
67
  node = edges[0]["node"]
53
68
  webhook_id = node["id"].to_s
54
69
  current_address = node["endpoint"]["arn"].to_s
70
+ fields = node["includeFields"] || []
71
+ metafield_namespaces = node["metafieldNamespaces"] || []
72
+ filter = node["filter"].to_s
55
73
  end
56
- { webhook_id: webhook_id, current_address: current_address }
74
+ { webhook_id: webhook_id, current_address: current_address, fields: fields,
75
+ metafield_namespaces: metafield_namespaces, filter: filter, }
57
76
  end
58
77
  end
59
78
  end
@@ -20,7 +20,8 @@ module ShopifyAPI
20
20
 
21
21
  sig { override.returns(T::Hash[Symbol, String]) }
22
22
  def subscription_args
23
- { callbackUrl: callback_address, includeFields: fields, metafieldNamespaces: metafield_namespaces }.compact
23
+ { callbackUrl: callback_address, includeFields: fields,
24
+ metafieldNamespaces: metafield_namespaces, filter: filter, }.compact
24
25
  end
25
26
 
26
27
  sig { override.params(webhook_id: T.nilable(String)).returns(String) }
@@ -36,6 +37,9 @@ module ShopifyAPI
36
37
  edges {
37
38
  node {
38
39
  id
40
+ includeFields
41
+ metafieldNamespaces
42
+ filter
39
43
  endpoint {
40
44
  __typename
41
45
  ... on WebhookHttpEndpoint {
@@ -49,10 +53,21 @@ module ShopifyAPI
49
53
  QUERY
50
54
  end
51
55
 
52
- sig { override.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) }
56
+ sig do
57
+ override.params(body: T::Hash[String, T.untyped]).returns({
58
+ webhook_id: T.nilable(String),
59
+ current_address: T.nilable(String),
60
+ fields: T::Array[String],
61
+ metafield_namespaces: T::Array[String],
62
+ filter: T.nilable(String),
63
+ })
64
+ end
53
65
  def parse_check_result(body)
54
66
  edges = body.dig("data", "webhookSubscriptions", "edges") || {}
55
67
  webhook_id = nil
68
+ fields = []
69
+ metafield_namespaces = []
70
+ filter = nil
56
71
  current_address = nil
57
72
  unless edges.empty?
58
73
  node = edges[0]["node"]
@@ -63,8 +78,12 @@ module ShopifyAPI
63
78
  else
64
79
  node["callbackUrl"].to_s
65
80
  end
81
+ fields = node["includeFields"] || []
82
+ metafield_namespaces = node["metafieldNamespaces"] || []
83
+ filter = node["filter"].to_s
66
84
  end
67
- { webhook_id: webhook_id, current_address: current_address }
85
+ { webhook_id: webhook_id, current_address: current_address, fields: fields,
86
+ metafield_namespaces: metafield_namespaces, filter: filter, }
68
87
  end
69
88
  end
70
89
  end
@@ -18,7 +18,7 @@ module ShopifyAPI
18
18
  project = project_topic_pair[0]
19
19
  topic = project_topic_pair[1]
20
20
  { pubSubProject: project, pubSubTopic: topic, includeFields: fields,
21
- metafieldNamespaces: metafield_namespaces, }.compact
21
+ metafieldNamespaces: metafield_namespaces, filter: filter, }.compact
22
22
  end
23
23
 
24
24
  sig { override.params(webhook_id: T.nilable(String)).returns(String) }
@@ -34,6 +34,9 @@ module ShopifyAPI
34
34
  edges {
35
35
  node {
36
36
  id
37
+ includeFields
38
+ metafieldNamespaces
39
+ filter
37
40
  endpoint {
38
41
  __typename
39
42
  ... on WebhookPubSubEndpoint {
@@ -48,17 +51,33 @@ module ShopifyAPI
48
51
  QUERY
49
52
  end
50
53
 
51
- sig { override.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) }
54
+ sig do
55
+ override.params(body: T::Hash[String, T.untyped]).returns({
56
+ webhook_id: T.nilable(String),
57
+ current_address: T.nilable(String),
58
+ fields: T::Array[String],
59
+ metafield_namespaces: T::Array[String],
60
+ filter: T.nilable(String),
61
+ })
62
+ end
52
63
  def parse_check_result(body)
53
64
  edges = body.dig("data", "webhookSubscriptions", "edges") || {}
54
65
  webhook_id = nil
66
+ fields = []
67
+ metafield_namespaces = []
68
+ filter = nil
55
69
  current_address = nil
56
70
  unless edges.empty?
57
71
  node = edges[0]["node"]
58
72
  webhook_id = node["id"].to_s
59
- current_address = "pubsub://#{node["endpoint"]["pubSubProject"]}:#{node["endpoint"]["pubSubTopic"]}"
73
+ current_address =
74
+ "pubsub://#{node["endpoint"]["pubSubProject"]}:#{node["endpoint"]["pubSubTopic"]}"
75
+ fields = node["includeFields"] || []
76
+ metafield_namespaces = node["metafieldNamespaces"] || []
77
+ filter = node["filter"].to_s
60
78
  end
61
- { webhook_id: webhook_id, current_address: current_address }
79
+ { webhook_id: webhook_id, current_address: current_address, fields: fields,
80
+ metafield_namespaces: metafield_namespaces, filter: filter, }
62
81
  end
63
82
  end
64
83
  end
@@ -19,23 +19,25 @@ module ShopifyAPI
19
19
  path: String,
20
20
  handler: T.nilable(T.any(Handler, WebhookHandler)),
21
21
  fields: T.nilable(T.any(String, T::Array[String])),
22
+ filter: T.nilable(String),
22
23
  metafield_namespaces: T.nilable(T::Array[String])).void
23
24
  end
24
- def add_registration(topic:, delivery_method:, path:, handler: nil, fields: nil, metafield_namespaces: nil)
25
+ def add_registration(topic:, delivery_method:, path:, handler: nil, fields: nil, filter: nil,
26
+ metafield_namespaces: nil)
25
27
  @registry[topic] = case delivery_method
26
28
  when :pub_sub
27
29
  Registrations::PubSub.new(topic: topic, path: path, fields: fields,
28
- metafield_namespaces: metafield_namespaces)
30
+ metafield_namespaces: metafield_namespaces, filter: filter)
29
31
  when :event_bridge
30
32
  Registrations::EventBridge.new(topic: topic, path: path, fields: fields,
31
- metafield_namespaces: metafield_namespaces)
33
+ metafield_namespaces: metafield_namespaces, filter: filter)
32
34
  when :http
33
35
  unless handler
34
36
  raise Errors::InvalidWebhookRegistrationError, "Cannot create an Http registration without a handler."
35
37
  end
36
38
 
37
39
  Registrations::Http.new(topic: topic, path: path, handler: handler,
38
- fields: fields, metafield_namespaces: metafield_namespaces)
40
+ fields: fields, metafield_namespaces: metafield_namespaces, filter: filter)
39
41
  else
40
42
  raise Errors::InvalidWebhookRegistrationError,
41
43
  "Unsupported delivery method #{delivery_method}. Allowed values: {:http, :pub_sub, :event_bridge}."
@@ -219,8 +221,16 @@ module ShopifyAPI
219
221
  check_response = client.query(query: registration.build_check_query, response_as_struct: false)
220
222
  raise Errors::WebhookRegistrationError,
221
223
  "Failed to check if webhook was already registered" unless check_response.ok?
224
+
222
225
  parsed_check_result = registration.parse_check_result(T.cast(check_response.body, T::Hash[String, T.untyped]))
223
- must_register = parsed_check_result[:current_address] != registration.callback_address
226
+ registration_fields = registration.fields || []
227
+ registration_metafield_namespaces = registration.metafield_namespaces || []
228
+ registration_filter = registration.filter
229
+
230
+ must_register = parsed_check_result[:current_address] != registration.callback_address ||
231
+ parsed_check_result[:fields].sort != registration_fields.sort ||
232
+ parsed_check_result[:metafield_namespaces].sort != registration_metafield_namespaces.sort ||
233
+ parsed_check_result[:filter] != registration_filter
224
234
 
225
235
  { webhook_id: parsed_check_result[:webhook_id], must_register: must_register }
226
236
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.6.0
4
+ version: 14.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-02 00:00:00.000000000 Z
11
+ date: 2024-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -1157,7 +1157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1157
1157
  - !ruby/object:Gem::Version
1158
1158
  version: '0'
1159
1159
  requirements: []
1160
- rubygems_version: 3.5.20
1160
+ rubygems_version: 3.5.23
1161
1161
  signing_key:
1162
1162
  specification_version: 4
1163
1163
  summary: The gem for accessing the Shopify API