shopify_api 14.6.0 → 14.7.0

Sign up to get free protection for your applications and to get access to all the features.
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