shopify_api 13.4.0 → 14.0.1

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: d837a507476d9d344892dc7667fed5cf1fcc20ac37c5bc5488e4a2583b3dab5c
4
- data.tar.gz: b385d9e275df49f00fd8aac9bba99c6df69a9f111afd92285e12e2b411d734e5
3
+ metadata.gz: 0eceb8b083a02fd062ebc545e6d4a60b1b4c11fec77bead21bef052f1c184b0a
4
+ data.tar.gz: 6d47eb134e26c9536db9ee6225abf3edf00a07c15060aa6cae6d490b49347151
5
5
  SHA512:
6
- metadata.gz: 0f1de95b3fd22f3f7494914d74647cb51607e5302c3bbf23c7d29e159f1a90f21509d2c7ade5606287dd698ce67e7ec018bf0fa0a099f4034eefdac363d0f20f
7
- data.tar.gz: 58e4c9c8c67bed4996f056e0219d45c38d589feb2e04e3902302afb71e3f282b2138f85b813d394a953c7c323cfd2f42c71464931fdd7f83327c145921b18d96
6
+ metadata.gz: 38fb4a844e00437ff2d2e93cc6c931c13ace7511aec1dcbf49585a6ea22a0f4db59cc472d46b257424645b20de2740600d8e429ed21435cf64448df6aea5f3e4
7
+ data.tar.gz: d3d4102189174b84791a7f0065bf633b548b4034fcff31305b7d350de3a3d07fdc7b6fe1a21dd8e7286583656d1bec7517ed4662aa429c17d00fcfdc9176eba2
@@ -6,35 +6,41 @@ labels: "Type: Bug 🐛"
6
6
 
7
7
  # Issue summary
8
8
 
9
- <!--
10
-
11
- Write a short description of the issue here. Please provide any details or logs that
12
- can help us debug it.
9
+ Before opening this issue, I have:
10
+
11
+ - [ ] Upgraded to the latest version of the package
12
+ - `shopify_api` version:
13
+ - Ruby version:
14
+ - Operating system:
15
+ - [ ] Set `log_level: :debug` [in my configuration](https://github.com/Shopify/shopify-api-ruby#setup-shopify-context), if applicable
16
+ - [ ] Found a reliable way to reproduce the problem that indicates it's a problem with the package
17
+ - [ ] Looked for similar issues in this repository
18
+ - [ ] Checked that this isn't an issue with a Shopify API
19
+ - If it is, please create a post in the [Shopify community forums](https://community.shopify.com/c/partners-and-developers/ct-p/appdev) or report it to [Shopify Partner Support](https://help.shopify.com/en/support/partners/org-select)
13
20
 
14
- Increase the logs as described in the README by setting log_level to :debug, and paste the relevant portion here.
15
-
16
- Learn more: https://github.com/Shopify/shopify-api-ruby#setup-shopify-context
21
+ <!--
22
+ Write a short description of the issue here.
17
23
 
24
+ We can only fix issues for which there is a clear reproduction scenario.
25
+ The more context you can provide, the easier it becomes for us to investigate and fix the issue.
18
26
  -->
19
27
 
20
- - `shopify_api` version:
21
- - Ruby version:
22
- - Operating system:
23
-
24
- ```
25
- // Paste any relevant logs here
26
- ```
27
-
28
28
  ## Expected behavior
29
29
 
30
- <!-- What do you think should happen? -->
30
+ What do you think should happen?
31
31
 
32
32
  ## Actual behavior
33
33
 
34
- <!-- What actually happens? -->
34
+ What actually happens?
35
35
 
36
36
  ## Steps to reproduce the problem
37
37
 
38
38
  1.
39
39
  1.
40
40
  1.
41
+
42
+ ## Debug logs
43
+
44
+ ```
45
+ // Paste any relevant logs here
46
+ ```
@@ -11,7 +11,6 @@ jobs:
11
11
  strategy:
12
12
  matrix:
13
13
  version:
14
- - 2.7
15
14
  - 3.0
16
15
  - 3.1
17
16
  steps:
data/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 14.0.1
8
+ - [#1288](https://github.com/Shopify/shopify-api-ruby/pull/1288) Fix FeatureDeprecatedError being raised without a message.
9
+ - [1290](https://github.com/Shopify/shopify-api-ruby/pull/1290) Move deprecation of `ShopifyAPI::Webhooks::Handler#handle` to version 15.0.0
10
+
11
+ ## 14.0.0
12
+ - [#1274](https://github.com/Shopify/shopify-api-ruby/pull/1274) ⚠️ [Breaking] Update sorbet and rbi dependencies. Remove support for ruby 2.7. Minimum required Ruby version is 3.0
13
+ - [#1282](https://github.com/Shopify/shopify-api-ruby/pull/1282) Fixes a bug where diffing attributes to update not take into account of Array changes and required ids.
14
+ - [#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.
15
+ - [#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.
16
+ - [#1275](https://github.com/Shopify/shopify-api-ruby/pull/1275) Allow adding custom headers in REST Resource HTTP calls.
17
+
7
18
  ## 13.4.0
8
19
  - [#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.
9
20
  - [#1257](https://github.com/Shopify/shopify-api-ruby/pull/1257) Add `api_call_limit` and `retry_request_after` to REST resources to expose rate limit information.
@@ -0,0 +1,46 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all
5
+ people who contribute through reporting issues, posting feature
6
+ requests, updating documentation, submitting pull requests or patches,
7
+ and other activities.
8
+
9
+ We are committed to making participation in this project a
10
+ harassment-free experience for everyone, regardless of level of
11
+ experience, gender, gender identity and expression, sexual orientation,
12
+ disability, personal appearance, body size, race, ethnicity, age,
13
+ religion, or nationality.
14
+
15
+ Examples of unacceptable behavior by participants include:
16
+
17
+ - The use of sexualized language or imagery
18
+ - Personal attacks
19
+ - Trolling or insulting/derogatory comments
20
+ - Public or private harassment
21
+ - Publishing other's private information, such as physical or electronic
22
+ addresses, without explicit permission
23
+ - Other unethical or unprofessional conduct
24
+
25
+ Project maintainers have the right and responsibility to remove, edit,
26
+ or reject comments, commits, code, wiki edits, issues, and other
27
+ contributions that are not aligned to this Code of Conduct, or to ban
28
+ temporarily or permanently any contributor for other behaviors that they
29
+ deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ By adopting this Code of Conduct, project maintainers commit themselves
32
+ to fairly and consistently applying these principles to every aspect of
33
+ managing this project. Project maintainers who do not follow or enforce
34
+ the Code of Conduct may be permanently removed from the project team.
35
+
36
+ This Code of Conduct applies both within project spaces and in public
37
+ spaces when an individual is representing the project or its community.
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behavior may
40
+ be reported by contacting a project maintainer at <opensource@shopify.com>.
41
+ All complaints will be reviewed and investigated and will result in a response
42
+ that is deemed necessary and appropriate to the circumstances. Maintainers are
43
+ obligated to maintain confidentiality with regard to the reporter of an incident.
44
+
45
+ This Code of Conduct is adapted from the Contributor Covenant, version
46
+ 1.3.0, available from http://contributor-covenant.org/version/1/3/0/
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (13.4.0)
4
+ shopify_api (14.0.1)
5
5
  activesupport
6
6
  concurrent-ruby
7
7
  hash_diff
@@ -16,7 +16,7 @@ PATH
16
16
  GEM
17
17
  remote: https://rubygems.org/
18
18
  specs:
19
- activesupport (7.1.2)
19
+ activesupport (7.1.3.2)
20
20
  base64
21
21
  bigdecimal
22
22
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -30,26 +30,26 @@ GEM
30
30
  public_suffix (>= 2.0.2, < 5.0)
31
31
  ast (2.4.2)
32
32
  base64 (0.2.0)
33
- bigdecimal (3.1.5)
33
+ bigdecimal (3.1.6)
34
34
  byebug (11.1.3)
35
35
  coderay (1.1.3)
36
- concurrent-ruby (1.2.2)
36
+ concurrent-ruby (1.2.3)
37
37
  connection_pool (2.4.1)
38
38
  crack (0.4.5)
39
39
  rexml
40
- diff-lcs (1.5.0)
41
- drb (2.2.0)
42
- ruby2_keywords
40
+ drb (2.2.1)
41
+ erubi (1.12.0)
43
42
  fakefs (1.4.1)
44
43
  hash_diff (1.1.1)
45
44
  hashdiff (1.0.1)
46
45
  httparty (0.21.0)
47
46
  mini_mime (>= 1.0.0)
48
47
  multi_xml (>= 0.5.2)
49
- i18n (1.14.1)
48
+ i18n (1.14.4)
50
49
  concurrent-ruby (~> 1.0)
51
50
  json (2.6.2)
52
- jwt (2.7.1)
51
+ jwt (2.8.1)
52
+ base64
53
53
  method_source (1.0.0)
54
54
  mini_mime (1.1.5)
55
55
  minitest (5.15.0)
@@ -60,25 +60,25 @@ GEM
60
60
  oj (3.16.3)
61
61
  bigdecimal (>= 3.0)
62
62
  openssl (3.2.0)
63
- parallel (1.22.1)
64
- parser (3.2.2.4)
63
+ parallel (1.24.0)
64
+ parser (3.3.0.5)
65
65
  ast (~> 2.4.1)
66
66
  racc
67
- pry (0.14.1)
67
+ prettier_print (1.2.1)
68
+ prism (0.21.0)
69
+ pry (0.14.2)
68
70
  coderay (~> 1.1)
69
71
  method_source (~> 1.0)
70
72
  pry-byebug (3.10.1)
71
73
  byebug (~> 11.0)
72
74
  pry (>= 0.13, < 0.15)
73
75
  public_suffix (4.0.6)
74
- racc (1.7.1)
76
+ racc (1.7.3)
75
77
  rainbow (3.1.1)
76
78
  rake (13.0.6)
77
- rbi (0.0.15)
78
- ast
79
- parser (>= 2.6.4.0)
79
+ rbi (0.1.8)
80
+ prism (>= 0.18.0, < 0.22)
80
81
  sorbet-runtime (>= 0.5.9204)
81
- unparser
82
82
  regexp_parser (2.5.0)
83
83
  rexml (3.2.5)
84
84
  rubocop (1.36.0)
@@ -98,49 +98,44 @@ GEM
98
98
  rubocop-sorbet (0.6.11)
99
99
  rubocop (>= 0.90.0)
100
100
  ruby-progressbar (1.11.0)
101
- ruby2_keywords (0.0.5)
102
101
  securerandom (0.3.1)
103
- sorbet (0.5.10438)
104
- sorbet-static (= 0.5.10438)
105
- sorbet-runtime (0.5.10438)
106
- sorbet-static (0.5.10438-universal-darwin-21)
107
- sorbet-static (0.5.10438-universal-darwin-22)
108
- sorbet-static (0.5.10438-x86_64-linux)
109
- sorbet-static-and-runtime (0.5.10438)
110
- sorbet (= 0.5.10438)
111
- sorbet-runtime (= 0.5.10438)
112
- spoom (1.1.11)
113
- sorbet (>= 0.5.9204)
114
- sorbet-runtime (>= 0.5.9204)
102
+ sorbet (0.5.11230)
103
+ sorbet-static (= 0.5.11230)
104
+ sorbet-runtime (0.5.11230)
105
+ sorbet-static (0.5.11230-universal-darwin)
106
+ sorbet-static (0.5.11230-x86_64-linux)
107
+ sorbet-static-and-runtime (0.5.11230)
108
+ sorbet (= 0.5.11230)
109
+ sorbet-runtime (= 0.5.11230)
110
+ spoom (1.2.4)
111
+ erubi (>= 1.10.0)
112
+ sorbet-static-and-runtime (>= 0.5.10187)
113
+ syntax_tree (>= 6.1.1)
115
114
  thor (>= 0.19.2)
116
- tapioca (0.10.2)
117
- bundler (>= 1.17.3)
115
+ syntax_tree (6.2.0)
116
+ prettier_print (>= 1.2.0)
117
+ tapioca (0.12.0)
118
+ bundler (>= 2.2.25)
118
119
  netrc (>= 0.11.0)
119
120
  parallel (>= 1.21.0)
120
- pry (>= 0.12.2)
121
- rbi (~> 0.0.0, >= 0.0.14)
122
- sorbet-static-and-runtime (>= 0.5.9204)
123
- spoom (~> 1.1.0, >= 1.1.11)
121
+ rbi (>= 0.1.4, < 0.2)
122
+ sorbet-static-and-runtime (>= 0.5.10820)
123
+ spoom (~> 1.2.0, >= 1.2.0)
124
124
  thor (>= 1.2.0)
125
125
  yard-sorbet
126
- thor (1.2.1)
126
+ thor (1.3.0)
127
127
  tzinfo (2.0.6)
128
128
  concurrent-ruby (~> 1.0)
129
129
  unicode-display_width (2.3.0)
130
- unparser (0.6.5)
131
- diff-lcs (~> 1.3)
132
- parser (>= 3.1.0)
133
130
  webmock (3.14.0)
134
131
  addressable (>= 2.8.0)
135
132
  crack (>= 0.3.2)
136
133
  hashdiff (>= 0.4.0, < 2.0.0)
137
- webrick (1.7.0)
138
- yard (0.9.28)
139
- webrick (~> 1.7.0)
140
- yard-sorbet (0.7.0)
134
+ yard (0.9.34)
135
+ yard-sorbet (0.8.1)
141
136
  sorbet-runtime (>= 0.5)
142
137
  yard (>= 0.9)
143
- zeitwerk (2.6.12)
138
+ zeitwerk (2.6.13)
144
139
 
145
140
  PLATFORMS
146
141
  arm64-darwin-21
data/dev.yml CHANGED
@@ -3,8 +3,9 @@ name: shopify-api
3
3
  type: ruby
4
4
 
5
5
  up:
6
- - ruby: 3.0.3
7
- - bundler
6
+ - ruby: 3.0.6
7
+ - bundler:
8
+ gemfile: Gemfile
8
9
 
9
10
  commands:
10
11
  console:
data/docs/usage/rest.md CHANGED
@@ -69,18 +69,18 @@ Typical methods provided for each resources are:
69
69
  Full list of methods can be found on each of the resource class.
70
70
  - Path:
71
71
  - https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/rest/resources/#{version}/#{resource}.rb
72
- - Example for `Order` resource on `2023-04` version:
73
- - https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/rest/resources/2023_04/order.rb
72
+ - Example for `Order` resource on `2024-01` version:
73
+ - https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/rest/resources/2024_01/order.rb
74
74
 
75
- ### Usage Examples
76
- ⚠️ Reference documentation on [shopify.dev](https://shopify.dev/docs/api/admin-rest) contains more examples on how to use each REST Resources.
75
+ ### The `save` method
77
76
 
78
- ```Ruby
79
- # Find and update a customer email
80
- customer = ShopifyAPI::Customer.find(id: customer_id)
81
- customer.email = "steve-lastnameson@example.com"
82
- customer.save!
77
+ The `save` or `save!` method on a resource allows you to `create` or `update` that resource.
78
+
79
+ #### Create a new resource
83
80
 
81
+ To create a new resource using the `save` or `save!` method, you can initialize the resource with a hash of values or simply assigning them manually. For example:
82
+
83
+ ```Ruby
84
84
  # Create a new product from hash
85
85
  product_properties = {
86
86
  title: "My awesome product"
@@ -88,10 +88,63 @@ product_properties = {
88
88
  product = ShopifyAPI::Product.new(from_hash: product_properties)
89
89
  product.save!
90
90
 
91
- # Create a product manually
91
+ # Create a new product manually
92
92
  product = ShopifyAPI::Product.new
93
93
  product.title = "Another one"
94
94
  product.save!
95
+ ```
96
+
97
+ #### Update an existing resource
98
+
99
+ To update an existing resource using the `save` or `save!` method, you'll need to fetch the resource from Shopify first. Then, you can manually assign new values to the resource before calling `save` or `save!`. For example:
100
+
101
+ ```Ruby
102
+ # Update a product's title
103
+ product = ShopifyAPI::Product.find(id: product_id)
104
+ product.title = "My new title"
105
+ product.save!
106
+
107
+ # Remove a line item from a draft order
108
+ draft_order = ShopifyAPI::DraftOrder.find(id: draft_order_id)
109
+
110
+ new_line_items = draft_order.line_items.reject { |line_item| line_item["id"] == 12345 }
111
+ draft_order.line_items = new_line_items
112
+
113
+ draft_order.save!
114
+ ```
115
+
116
+ > [!IMPORTANT]
117
+ > If you need to unset an existing value,
118
+ > please explicitly set that attribute to `nil` or empty values such as `[]` or `{}`. For example:
119
+ >
120
+ > ```Ruby
121
+ > # Removes shipping address from draft_order
122
+ > draft_order.shipping_address = {}
123
+ > draft_order.save!
124
+ > ```
125
+ >
126
+ > This is because only modified values are sent to the API, so if `shipping_address` is not "modified" to `{}`. It won't be part of the PUT request payload
127
+
128
+ When updating a resource, only the modified attributes, the resource's primary key, and required parameters are sent to the API. The primary key is usually the `id` attribute of the resource, but it can vary if the `primary_key` method is overwritten in the resource's class. The required parameters are identified using the path parameters of the `PUT` endpoint of the resource.
129
+
130
+ ### Headers
131
+ You can add custom headers to the HTTP calls made by methods like `find`, `delete`, `all`, `count`
132
+ by setting the `headers` attribute on the `ShopifyAPI::Rest::Base` class in an initializer, like so:
133
+
134
+ ```ruby
135
+ ShopifyAPI::Rest::Base.headers = { "X-Custom-Header" => "Custom Value" }
136
+ # `find` will call the API endpoint with the custom header
137
+ ShopifyAPI::Customer.find(id: customer_id)
138
+ ```
139
+
140
+ ### Usage Examples
141
+ ⚠️ The [API reference documentation](https://shopify.dev/docs/api/admin-rest) contains more examples on how to use each REST Resources.
142
+
143
+ ```Ruby
144
+ # Find and update a customer email
145
+ customer = ShopifyAPI::Customer.find(id: customer_id)
146
+ customer.email = "steve-lastnameson@example.com"
147
+ customer.save!
95
148
 
96
149
  # Get all orders
97
150
  orders = ShopifyAPI::Orders.all
@@ -7,10 +7,37 @@ If using in the Rails framework, we highly recommend you use the [shopify_app](h
7
7
 
8
8
  ## Create a Webhook Handler
9
9
 
10
- 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:
10
+ 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:
11
+
12
+ `data` will have the following keys
13
+ - `topic`, `String` - The topic of the webhook
14
+ - `shop`, `String` - The shop domain of the webhook
15
+ - `body`, `T::Hash[String, T.untyped]`- The body of the webhook
16
+ - `webhook_id`, `String` - The id of the webhook event to [avoid duplicates](https://shopify.dev/docs/apps/webhooks/best-practices#ignore-duplicates)
17
+ - `api_version`, `String` - The api version of the webhook
18
+
19
+ ```ruby
20
+ module WebhookHandler
21
+ extend ShopifyAPI::Webhooks::WebhookHandler
22
+
23
+ class << self
24
+ def handle_webhook(data:)
25
+ puts "Received webhook! topic: #{data.topic} shop: #{data.shop} body: #{data.body} webhook_id: #{data.webhook_id} api_version: #{data.api_version"
26
+ end
27
+ end
28
+ end
29
+ ```
30
+
31
+ **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.
32
+
33
+ ### Best Practices
34
+ 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.
35
+
36
+ ### Webhook Handler for versions 13.4.0 and prior
37
+ 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:
11
38
 
12
39
  ```ruby
13
- module WebhookHandler
40
+ module WebhookHandler
14
41
  extend ShopifyAPI::Webhooks::Handler
15
42
 
16
43
  class << self
@@ -21,25 +48,23 @@ module WebhookHandler
21
48
  end
22
49
  ```
23
50
 
24
- **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.
25
-
26
51
  ## Add to Webhook Registry
27
52
 
28
53
  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:
29
54
 
30
55
  ```ruby
31
- registration = ShopifyAPI::Webhooks::Registry.add_registration(topic: "orders/create",
56
+ registration = ShopifyAPI::Webhooks::Registry.add_registration(topic: "orders/create",
32
57
  delivery_method: :http,
33
58
  handler: WebhookHandler,
34
- path: 'callback/orders/create')
59
+ path: 'callback/orders/create')
35
60
  ```
36
61
  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:
37
62
 
38
63
  ```ruby
39
64
  registration = ShopifyAPI::Webhooks::Registry.add_registration(
40
- topic: "orders/create",
41
- delivery_method: :http,
42
- handler: WebhookHandler,
65
+ topic: "orders/create",
66
+ delivery_method: :http,
67
+ handler: WebhookHandler,
43
68
  path: 'callback/orders/create',
44
69
  fields: ["number","note"] # this can also be a single comma separated string
45
70
  )
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ShopifyAPI
5
+ module Auth
6
+ module Oauth
7
+ class AccessTokenResponse < T::Struct
8
+ extend T::Sig
9
+
10
+ const :access_token, String
11
+ const :scope, String
12
+ const :session, T.nilable(String)
13
+ const :expires_in, T.nilable(Integer)
14
+ const :associated_user, T.nilable(AssociatedUser)
15
+ const :associated_user_scope, T.nilable(String)
16
+
17
+ sig { returns(T::Boolean) }
18
+ def online_token?
19
+ !associated_user.nil?
20
+ end
21
+
22
+ alias_method :eql?, :==
23
+ sig { params(other: T.nilable(AccessTokenResponse)).returns(T::Boolean) }
24
+ def ==(other)
25
+ return false unless other
26
+
27
+ access_token == other.access_token &&
28
+ scope == other.scope &&
29
+ session == other.session &&
30
+ expires_in == other.expires_in &&
31
+ associated_user == other.associated_user &&
32
+ associated_user_scope == other.associated_user_scope
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -89,7 +89,8 @@ module ShopifyAPI
89
89
  end
90
90
 
91
91
  session_params = T.cast(response.body, T::Hash[String, T.untyped]).to_h
92
- session = create_new_session(session_params, auth_query.shop)
92
+ session = Session.from(shop: auth_query.shop,
93
+ access_token_response: Oauth::AccessTokenResponse.from_hash(session_params))
93
94
 
94
95
  cookie = if Context.embedded?
95
96
  SessionCookie.new(
@@ -105,38 +106,6 @@ module ShopifyAPI
105
106
 
106
107
  { session: session, cookie: cookie }
107
108
  end
108
-
109
- private
110
-
111
- sig { params(session_params: T::Hash[String, T.untyped], shop: String).returns(Session) }
112
- def create_new_session(session_params, shop)
113
- session_params = session_params.to_h { |k, v| [k.to_sym, v] }
114
-
115
- scope = session_params[:scope]
116
-
117
- is_online = !session_params[:associated_user].nil?
118
-
119
- if is_online
120
- associated_user = AssociatedUser.new(session_params[:associated_user].to_h { |k, v| [k.to_sym, v] })
121
- expires = Time.now + session_params[:expires_in].to_i
122
- associated_user_scope = session_params[:associated_user_scope]
123
- id = "#{shop}_#{associated_user.id}"
124
- else
125
- id = "offline_#{shop}"
126
- end
127
-
128
- Session.new(
129
- id: id,
130
- shop: shop,
131
- access_token: session_params[:access_token],
132
- scope: scope,
133
- is_online: is_online,
134
- associated_user_scope: associated_user_scope,
135
- associated_user: associated_user,
136
- expires: expires,
137
- shopify_session_id: session_params[:session],
138
- )
139
- end
140
109
  end
141
110
  end
142
111
  end
@@ -89,6 +89,32 @@ module ShopifyAPI
89
89
  end
90
90
  end
91
91
 
92
+ sig { params(shop: String, access_token_response: Oauth::AccessTokenResponse).returns(Session) }
93
+ def from(shop:, access_token_response:)
94
+ is_online = access_token_response.online_token?
95
+
96
+ if is_online
97
+ associated_user = T.must(access_token_response.associated_user)
98
+ expires = Time.now + access_token_response.expires_in.to_i
99
+ associated_user_scope = access_token_response.associated_user_scope
100
+ id = "#{shop}_#{associated_user.id}"
101
+ else
102
+ id = "offline_#{shop}"
103
+ end
104
+
105
+ new(
106
+ id: id,
107
+ shop: shop,
108
+ access_token: access_token_response.access_token,
109
+ scope: access_token_response.scope,
110
+ is_online: is_online,
111
+ associated_user_scope: associated_user_scope,
112
+ associated_user: associated_user,
113
+ expires: expires,
114
+ shopify_session_id: access_token_response.session,
115
+ )
116
+ end
117
+
92
118
  sig { params(str: String).returns(Session) }
93
119
  def deserialize(str)
94
120
  Oj.load(str)
@@ -0,0 +1,80 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ShopifyAPI
5
+ module Auth
6
+ module TokenExchange
7
+ extend T::Sig
8
+
9
+ TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
10
+ ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token"
11
+
12
+ class RequestedTokenType < T::Enum
13
+ enums do
14
+ ONLINE_ACCESS_TOKEN = new("urn:shopify:params:oauth:token-type:online-access-token")
15
+ OFFLINE_ACCESS_TOKEN = new("urn:shopify:params:oauth:token-type:offline-access-token")
16
+ end
17
+ end
18
+
19
+ class << self
20
+ extend T::Sig
21
+
22
+ sig do
23
+ params(
24
+ shop: String,
25
+ session_token: String,
26
+ requested_token_type: RequestedTokenType,
27
+ ).returns(ShopifyAPI::Auth::Session)
28
+ end
29
+ def exchange_token(shop:, session_token:, requested_token_type:)
30
+ unless ShopifyAPI::Context.setup?
31
+ raise ShopifyAPI::Errors::ContextNotSetupError,
32
+ "ShopifyAPI::Context not setup, please call ShopifyAPI::Context.setup"
33
+ end
34
+ raise ShopifyAPI::Errors::UnsupportedOauthError,
35
+ "Cannot perform OAuth Token Exchange for private apps." if ShopifyAPI::Context.private?
36
+ raise ShopifyAPI::Errors::UnsupportedOauthError,
37
+ "Cannot perform OAuth Token Exchange for non embedded apps." unless ShopifyAPI::Context.embedded?
38
+
39
+ # Validate the session token content
40
+ ShopifyAPI::Auth::JwtPayload.new(session_token)
41
+
42
+ shop_session = ShopifyAPI::Auth::Session.new(shop: shop)
43
+ body = {
44
+ client_id: ShopifyAPI::Context.api_key,
45
+ client_secret: ShopifyAPI::Context.api_secret_key,
46
+ grant_type: TOKEN_EXCHANGE_GRANT_TYPE,
47
+ subject_token: session_token,
48
+ subject_token_type: ID_TOKEN_TYPE,
49
+ requested_token_type: requested_token_type.serialize,
50
+ }
51
+
52
+ client = Clients::HttpClient.new(session: shop_session, base_path: "/admin/oauth")
53
+ response = begin
54
+ client.request(
55
+ Clients::HttpRequest.new(
56
+ http_method: :post,
57
+ path: "access_token",
58
+ body: body,
59
+ body_type: "application/json",
60
+ ),
61
+ )
62
+ rescue ShopifyAPI::Errors::HttpResponseError => error
63
+ if error.code == 400 && error.response.body["error"] == "invalid_subject_token"
64
+ raise ShopifyAPI::Errors::InvalidJwtTokenError, "Session token was rejected by token exchange"
65
+ end
66
+
67
+ raise error
68
+ end
69
+
70
+ session_params = T.cast(response.body, T::Hash[String, T.untyped]).to_h
71
+
72
+ Session.from(
73
+ shop: shop,
74
+ access_token_response: Oauth::AccessTokenResponse.from_hash(session_params),
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -33,7 +33,7 @@ module ShopifyAPI
33
33
  def deprecated(message, version)
34
34
  return unless enabled_for_log_level?(:warn)
35
35
 
36
- raise Errors::FeatureDeprecatedError unless valid_version(version)
36
+ raise Errors::FeatureDeprecatedError, message unless valid_version(version)
37
37
 
38
38
  send_to_logger(:warn, message)
39
39
  end
@@ -75,7 +75,7 @@ module ShopifyAPI
75
75
  def valid_version(version)
76
76
  current_version = Gem::Version.create(ShopifyAPI::VERSION)
77
77
  deprecate_version = Gem::Version.create(version)
78
- current_version < deprecate_version
78
+ T.must(current_version) < deprecate_version
79
79
  end
80
80
  end
81
81
  end
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "active_support/inflector"
5
- require "hash_diff"
6
5
 
7
6
  module ShopifyAPI
8
7
  module Rest
@@ -11,8 +10,9 @@ module ShopifyAPI
11
10
  extend T::Helpers
12
11
  abstract!
13
12
 
14
- @has_one = T.let({}, T::Hash[Symbol, Class])
15
- @has_many = T.let({}, T::Hash[Symbol, Class])
13
+ @headers = T.let(nil, T.nilable(T::Hash[T.any(Symbol, String), String]))
14
+ @has_one = T.let({}, T::Hash[Symbol, T::Class[T.anything]])
15
+ @has_many = T.let({}, T::Hash[Symbol, T::Class[T.anything]])
16
16
  @paths = T.let([], T::Array[T::Hash[Symbol, T.any(T::Array[Symbol], String, Symbol)]])
17
17
  @custom_prefix = T.let(nil, T.nilable(String))
18
18
  @read_only_attributes = T.let([], T.nilable(T::Array[Symbol]))
@@ -55,12 +55,24 @@ module ShopifyAPI
55
55
  sig { returns(T.nilable(String)) }
56
56
  attr_reader :custom_prefix
57
57
 
58
- sig { returns(T::Hash[Symbol, Class]) }
58
+ sig { returns(T::Hash[Symbol, T::Class[T.anything]]) }
59
59
  attr_reader :has_many
60
60
 
61
- sig { returns(T::Hash[Symbol, Class]) }
61
+ sig { returns(T::Hash[Symbol, T::Class[T.anything]]) }
62
62
  attr_reader :has_one
63
63
 
64
+ sig { returns(T.nilable(T::Hash[T.any(Symbol, String), String])) }
65
+ attr_accessor :headers
66
+
67
+ sig { params(subclass: T::Class[T.anything]).returns(T.untyped) }
68
+ def inherited(subclass)
69
+ super
70
+
71
+ subclass.define_singleton_method(:headers) do
72
+ ShopifyAPI::Rest::Base.headers
73
+ end
74
+ end
75
+
64
76
  sig do
65
77
  params(
66
78
  session: T.nilable(Auth::Session),
@@ -74,7 +86,7 @@ module ShopifyAPI
74
86
  client = ShopifyAPI::Clients::Rest::Admin.new(session: session)
75
87
 
76
88
  path = T.must(get_path(http_method: :get, operation: :get, ids: ids))
77
- response = client.get(path: path, query: params.compact)
89
+ response = client.get(path: path, query: params.compact, headers: headers)
78
90
 
79
91
  instance_variable_get(:"@prev_page_info").value = response.prev_page_info
80
92
  instance_variable_get(:"@next_page_info").value = response.next_page_info
@@ -187,6 +199,21 @@ module ShopifyAPI
187
199
  custom_prefix ? "#{T.must(custom_prefix).sub(%r{\A/}, "")}/#{match}" : match
188
200
  end
189
201
 
202
+ sig do
203
+ params(
204
+ http_method: Symbol,
205
+ operation: Symbol,
206
+ ).returns(T.nilable(T::Array[Symbol]))
207
+ end
208
+ def get_path_ids(http_method:, operation:)
209
+ found_path = @paths.find do |path|
210
+ http_method == path[:http_method] && operation == path[:operation]
211
+ end
212
+ return nil if found_path.nil?
213
+
214
+ T.cast(found_path[:ids], T::Array[Symbol])
215
+ end
216
+
190
217
  sig do
191
218
  params(
192
219
  http_method: Symbol,
@@ -205,13 +232,13 @@ module ShopifyAPI
205
232
 
206
233
  case http_method
207
234
  when :get
208
- client.get(path: T.must(path), query: params.compact)
235
+ client.get(path: T.must(path), query: params.compact, headers: headers)
209
236
  when :post
210
- client.post(path: T.must(path), query: params.compact, body: body || {})
237
+ client.post(path: T.must(path), query: params.compact, body: body || {}, headers: headers)
211
238
  when :put
212
- client.put(path: T.must(path), query: params.compact, body: body || {})
239
+ client.put(path: T.must(path), query: params.compact, body: body || {}, headers: headers)
213
240
  when :delete
214
- client.delete(path: T.must(path), query: params.compact)
241
+ client.delete(path: T.must(path), query: params.compact, headers: headers)
215
242
  else
216
243
  raise Errors::InvalidHttpRequestError, "Invalid HTTP method: #{http_method}"
217
244
  end
@@ -341,6 +368,7 @@ module ShopifyAPI
341
368
  @client.delete(
342
369
  path: T.must(self.class.get_path(http_method: :delete, operation: :delete, entity: self)),
343
370
  query: params.compact,
371
+ headers: self.class.headers,
344
372
  )
345
373
  rescue ShopifyAPI::Errors::HttpResponseError => e
346
374
  @errors.errors << e
@@ -355,10 +383,16 @@ module ShopifyAPI
355
383
  sig { params(update_object: T::Boolean).void }
356
384
  def save(update_object: false)
357
385
  method = deduce_write_verb
386
+
387
+ body = {
388
+ self.class.json_body_name => attributes_to_update.merge(build_required_attributes(http_method: method)),
389
+ }
390
+
358
391
  response = @client.public_send(
359
392
  method,
360
- body: { self.class.json_body_name => attributes_to_update },
393
+ body: body,
361
394
  path: deduce_write_path(method),
395
+ headers: self.class.headers,
362
396
  )
363
397
 
364
398
  if update_object
@@ -374,22 +408,34 @@ module ShopifyAPI
374
408
 
375
409
  sig { returns(T::Hash[String, String]) }
376
410
  def attributes_to_update
377
- original_state_for_update = original_state.reject do |attribute, _|
411
+ updatable_attributes = original_state.reject do |attribute, _|
378
412
  self.class.read_only_attributes&.include?("@#{attribute}".to_sym)
379
413
  end
380
414
 
381
- diff = HashDiff::Comparison.new(
382
- deep_stringify_keys(original_state_for_update),
383
- deep_stringify_keys(to_hash(true)),
384
- ).left_diff
415
+ stringified_updatable_attributes = deep_stringify_keys(updatable_attributes)
416
+ stringified_new_attributes = deep_stringify_keys(to_hash(true))
417
+ ShopifyAPI::Utils::AttributesComparator.compare(
418
+ stringified_updatable_attributes,
419
+ stringified_new_attributes,
420
+ )
421
+ end
385
422
 
386
- diff.each do |attribute, value|
387
- if value.is_a?(Hash) && value[0] == HashDiff::NO_VALUE
388
- diff[attribute] = send(attribute)
389
- end
423
+ sig { params(http_method: Symbol).returns(T::Hash[String, T.untyped]) }
424
+ def build_required_attributes(http_method:)
425
+ required_attributes = {}
426
+
427
+ primary_key_value = send(self.class.primary_key)
428
+ unless primary_key_value.nil?
429
+ required_attributes[self.class.primary_key] = primary_key_value
430
+ end
431
+
432
+ path_ids = deduce_path_ids(http_method)
433
+ path_ids&.each do |path_id|
434
+ path_id_value = send(path_id)
435
+ required_attributes[path_id.to_s] = path_id_value unless path_id_value.nil?
390
436
  end
391
437
 
392
- diff
438
+ required_attributes
393
439
  end
394
440
 
395
441
  sig { returns(Symbol) }
@@ -409,6 +455,18 @@ module ShopifyAPI
409
455
  path
410
456
  end
411
457
 
458
+ sig { params(method: Symbol).returns(T.nilable(T::Array[Symbol])) }
459
+ def deduce_path_ids(method)
460
+ path_ids = self.class.get_path_ids(http_method: method, operation: method)
461
+
462
+ if path_ids.nil?
463
+ method = method == :post ? :put : :post
464
+ path_ids = self.class.get_path_ids(http_method: method, operation: method)
465
+ end
466
+
467
+ path_ids
468
+ end
469
+
412
470
  sig { params(hash: T::Hash[T.any(String, Symbol), T.untyped]).returns(T::Hash[String, String]) }
413
471
  def deep_stringify_keys(hash)
414
472
  hash.each_with_object({}) do |(key, value), result|
@@ -440,7 +498,7 @@ module ShopifyAPI
440
498
  sig do
441
499
  params(
442
500
  element: T.nilable(T.any(T::Hash[String, T.untyped], ShopifyAPI::Rest::Base)),
443
- attribute_class: Class,
501
+ attribute_class: T::Class[T.anything],
444
502
  saving: T::Boolean,
445
503
  ).returns(T.nilable(T::Hash[String, T.untyped]))
446
504
  end
@@ -0,0 +1,85 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "hash_diff"
5
+
6
+ module ShopifyAPI
7
+ module Utils
8
+ module AttributesComparator
9
+ class << self
10
+ extend T::Sig
11
+
12
+ sig do
13
+ params(
14
+ original_attributes: T::Hash[String, T.untyped],
15
+ updated_attributes: T::Hash[String, T.untyped],
16
+ ).returns(T::Hash[String, T.untyped])
17
+ end
18
+ def compare(original_attributes, updated_attributes)
19
+ attributes_diff = HashDiff::Comparison.new(
20
+ original_attributes,
21
+ updated_attributes,
22
+ ).left_diff
23
+
24
+ update_value = build_update_value(
25
+ attributes_diff,
26
+ reference_values: updated_attributes,
27
+ )
28
+
29
+ update_value
30
+ end
31
+
32
+ sig do
33
+ params(
34
+ diff: T::Hash[String, T.untyped],
35
+ path: T::Array[String],
36
+ reference_values: T::Hash[String, T.untyped],
37
+ ).returns(T::Hash[String, T.untyped])
38
+ end
39
+ def build_update_value(diff, path: [], reference_values: {})
40
+ new_hash = {}
41
+
42
+ diff.each do |key, value|
43
+ current_path = path + [key.to_s]
44
+
45
+ if value.is_a?(Hash)
46
+ has_numbered_key = value.keys.any? { |k| k.is_a?(Integer) }
47
+ ref_value = T.unsafe(reference_values).dig(*current_path)
48
+
49
+ if has_numbered_key && ref_value.is_a?(Array)
50
+ new_hash[key] = ref_value
51
+ else
52
+ new_value = build_update_value(value, path: current_path, reference_values: reference_values)
53
+
54
+ # Only add to new_hash if the user intentionally updates
55
+ # to empty value like `{}` or `[]`. For example:
56
+ #
57
+ # original = { "a" => { "foo" => 1 } }
58
+ # updated = { "a" => {} }
59
+ # diff = { "a" => { "foo" => HashDiff::NO_VALUE } }
60
+ # key = "a", new_value = {}, ref_value = {}
61
+ # new_hash = { "a" => {} }
62
+ #
63
+ # In addition, we omit cases where after removing `HashDiff::NO_VALUE`
64
+ # we only have `{}` left. For example:
65
+ #
66
+ # original = { "a" => { "foo" => 1, "bar" => 2} }
67
+ # updated = { "a" => { "foo" => 1 } }
68
+ # diff = { "a" => { "bar" => HashDiff::NO_VALUE } }
69
+ # key = "a", new_value = {}, ref_value = { "foo" => 1 }
70
+ # new_hash = {}
71
+ #
72
+ # new_hash is empty because nothing changes
73
+ new_hash[key] = new_value if !new_value.empty? || ref_value.empty?
74
+ end
75
+ elsif value != HashDiff::NO_VALUE
76
+ new_hash[key] = value
77
+ end
78
+ end
79
+
80
+ new_hash
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -27,7 +27,7 @@ module ShopifyAPI
27
27
  def validate_signature(verifiable_query, secret)
28
28
  received_signature = verifiable_query.hmac
29
29
  computed_signature = compute_signature(verifiable_query.to_signable_string, secret)
30
- OpenSSL.secure_compare(computed_signature, received_signature)
30
+ OpenSSL.secure_compare(computed_signature, T.must(received_signature))
31
31
  end
32
32
 
33
33
  sig { params(signable_string: String, secret: String).returns(String) }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ShopifyAPI
5
- VERSION = "13.4.0"
5
+ VERSION = "14.0.1"
6
6
  end
@@ -3,13 +3,36 @@
3
3
 
4
4
  module ShopifyAPI
5
5
  module Webhooks
6
+ class WebhookMetadata < T::Struct
7
+ const :topic, String
8
+ const :shop, String
9
+ const :body, T::Hash[String, T.untyped]
10
+ const :api_version, String
11
+ const :webhook_id, String
12
+ end
13
+
6
14
  module Handler
15
+ include Kernel
7
16
  extend T::Sig
8
17
  extend T::Helpers
9
18
  interface!
10
19
 
11
- sig { abstract.params(topic: String, shop: String, body: T::Hash[String, T.untyped]).void }
20
+ sig do
21
+ abstract.params(topic: String, shop: String, body: T::Hash[String, T.untyped]).void
22
+ end
12
23
  def handle(topic:, shop:, body:); end
13
24
  end
25
+
26
+ module WebhookHandler
27
+ include Kernel
28
+ extend T::Sig
29
+ extend T::Helpers
30
+ interface!
31
+
32
+ sig do
33
+ abstract.params(data: WebhookMetadata).void
34
+ end
35
+ def handle(data:); end
36
+ end
14
37
  end
15
38
  end
@@ -13,7 +13,7 @@ module ShopifyAPI
13
13
  sig { returns(String) }
14
14
  attr_reader :topic
15
15
 
16
- sig { returns(T.nilable(Handler)) }
16
+ sig { returns(T.nilable(T.any(Handler, WebhookHandler))) }
17
17
  attr_reader :handler
18
18
 
19
19
  sig { returns(T.nilable(T::Array[String])) }
@@ -23,7 +23,7 @@ module ShopifyAPI
23
23
  attr_reader :metafield_namespaces
24
24
 
25
25
  sig do
26
- params(topic: String, path: String, handler: T.nilable(Handler),
26
+ params(topic: String, path: String, handler: T.nilable(T.any(Handler, WebhookHandler)),
27
27
  fields: T.nilable(T.any(String, T::Array[String])),
28
28
  metafield_namespaces: T.nilable(T::Array[String])).void
29
29
  end
@@ -17,7 +17,7 @@ module ShopifyAPI
17
17
  params(topic: String,
18
18
  delivery_method: Symbol,
19
19
  path: String,
20
- handler: T.nilable(Handler),
20
+ handler: T.nilable(T.any(Handler, WebhookHandler)),
21
21
  fields: T.nilable(T.any(String, T::Array[String])),
22
22
  metafield_namespaces: T.nilable(T::Array[String])).void
23
23
  end
@@ -193,7 +193,18 @@ module ShopifyAPI
193
193
  raise Errors::NoWebhookHandler, "No webhook handler found for topic: #{request.topic}."
194
194
  end
195
195
 
196
- handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body)
196
+ if handler.is_a?(WebhookHandler)
197
+ handler.handle(data: WebhookMetadata.new(topic: request.topic, shop: request.shop,
198
+ body: request.parsed_body, api_version: request.api_version, webhook_id: request.webhook_id))
199
+ else
200
+ handler.handle(topic: request.topic, shop: request.shop, body: request.parsed_body)
201
+ warning = <<~WARNING
202
+ DEPRECATED: Use ShopifyAPI::Webhooks::WebhookHandler#handle instead of
203
+ ShopifyAPI::Webhooks::Handler#handle.
204
+ https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/webhooks.md#create-a-webhook-handler
205
+ WARNING
206
+ ShopifyAPI::Logger.deprecated(warning, "15.0.0")
207
+ end
197
208
  end
198
209
 
199
210
  private
@@ -22,6 +22,16 @@ module ShopifyAPI
22
22
  T.cast(@headers["x-shopify-shop-domain"], String)
23
23
  end
24
24
 
25
+ sig { returns(String) }
26
+ def api_version
27
+ T.cast(@headers["x-shopify-api-version"], String)
28
+ end
29
+
30
+ sig { returns(String) }
31
+ def webhook_id
32
+ T.cast(@headers["x-shopify-webhook-id"], String)
33
+ end
34
+
25
35
  sig { override.returns(String) }
26
36
  def to_signable_string
27
37
  @raw_body
data/lib/shopify_api.rb CHANGED
@@ -16,6 +16,7 @@ require "concurrent"
16
16
 
17
17
  require_relative "shopify_api/inflector"
18
18
  require_relative "shopify_api/admin_versions"
19
+ require_relative "shopify_api/webhooks/handler"
19
20
 
20
21
  loader = Zeitwerk::Loader.for_gem
21
22
  loader.inflector = ShopifyAPI::Inflector.new(__FILE__)
data/shopify_api.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
30
30
 
31
31
  s.license = "MIT"
32
32
 
33
- s.required_ruby_version = ">= 2.6"
33
+ s.required_ruby_version = ">= 3.0"
34
34
 
35
35
  s.add_runtime_dependency("activesupport")
36
36
  s.add_runtime_dependency("concurrent-ruby")
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: 13.4.0
4
+ version: 14.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-15 00:00:00.000000000 Z
11
+ date: 2024-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -276,6 +276,7 @@ files:
276
276
  - BREAKING_CHANGES_FOR_OLDER_VERSIONS.md
277
277
  - BREAKING_CHANGES_FOR_V10.md
278
278
  - CHANGELOG.md
279
+ - CODE_OF_CONDUCT.md
279
280
  - CONTRIBUTING.md
280
281
  - Gemfile
281
282
  - Gemfile.lock
@@ -302,9 +303,11 @@ files:
302
303
  - lib/shopify_api/auth/auth_scopes.rb
303
304
  - lib/shopify_api/auth/jwt_payload.rb
304
305
  - lib/shopify_api/auth/oauth.rb
306
+ - lib/shopify_api/auth/oauth/access_token_response.rb
305
307
  - lib/shopify_api/auth/oauth/auth_query.rb
306
308
  - lib/shopify_api/auth/oauth/session_cookie.rb
307
309
  - lib/shopify_api/auth/session.rb
310
+ - lib/shopify_api/auth/token_exchange.rb
308
311
  - lib/shopify_api/clients/graphql/admin.rb
309
312
  - lib/shopify_api/clients/graphql/client.rb
310
313
  - lib/shopify_api/clients/graphql/storefront.rb
@@ -938,6 +941,7 @@ files:
938
941
  - lib/shopify_api/rest/resources/2024_01/user.rb
939
942
  - lib/shopify_api/rest/resources/2024_01/variant.rb
940
943
  - lib/shopify_api/rest/resources/2024_01/webhook.rb
944
+ - lib/shopify_api/utils/attributes_comparator.rb
941
945
  - lib/shopify_api/utils/graphql_proxy.rb
942
946
  - lib/shopify_api/utils/hmac_validator.rb
943
947
  - lib/shopify_api/utils/http_utils.rb
@@ -1022,14 +1026,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
1022
1026
  requirements:
1023
1027
  - - ">="
1024
1028
  - !ruby/object:Gem::Version
1025
- version: '2.6'
1029
+ version: '3.0'
1026
1030
  required_rubygems_version: !ruby/object:Gem::Requirement
1027
1031
  requirements:
1028
1032
  - - ">="
1029
1033
  - !ruby/object:Gem::Version
1030
1034
  version: '0'
1031
1035
  requirements: []
1032
- rubygems_version: 3.5.4
1036
+ rubygems_version: 3.5.6
1033
1037
  signing_key:
1034
1038
  specification_version: 4
1035
1039
  summary: The gem for accessing the Shopify API