shopify_api 14.7.0 → 14.9.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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/close-waiting-for-response-issues.yml +2 -2
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +13 -0
  5. data/Gemfile.lock +1 -1
  6. data/docs/usage/graphql_storefront.md +1 -1
  7. data/docs/usage/oauth.md +41 -4
  8. data/docs/usage/rest.md +2 -0
  9. data/lib/shopify_api/admin_versions.rb +6 -1
  10. data/lib/shopify_api/auth/client_credentials.rb +52 -0
  11. data/lib/shopify_api/auth/jwt_payload.rb +18 -15
  12. data/lib/shopify_api/auth/oauth.rb +15 -1
  13. data/lib/shopify_api/auth/session.rb +4 -1
  14. data/lib/shopify_api/clients/http_client.rb +26 -1
  15. data/lib/shopify_api/clients/rest/admin.rb +5 -0
  16. data/lib/shopify_api/context.rb +13 -3
  17. data/lib/shopify_api/errors/disabled_resource_error.rb +8 -0
  18. data/lib/shopify_api/rest/resources/2025_01/abandoned_checkout.rb +194 -0
  19. data/lib/shopify_api/rest/resources/2025_01/access_scope.rb +62 -0
  20. data/lib/shopify_api/rest/resources/2025_01/apple_pay_certificate.rb +109 -0
  21. data/lib/shopify_api/rest/resources/2025_01/application_charge.rb +113 -0
  22. data/lib/shopify_api/rest/resources/2025_01/application_credit.rb +95 -0
  23. data/lib/shopify_api/rest/resources/2025_01/article.rb +269 -0
  24. data/lib/shopify_api/rest/resources/2025_01/asset.rb +122 -0
  25. data/lib/shopify_api/rest/resources/2025_01/assigned_fulfillment_order.rb +92 -0
  26. data/lib/shopify_api/rest/resources/2025_01/balance.rb +58 -0
  27. data/lib/shopify_api/rest/resources/2025_01/blog.rb +166 -0
  28. data/lib/shopify_api/rest/resources/2025_01/cancellation_request.rb +87 -0
  29. data/lib/shopify_api/rest/resources/2025_01/carrier_service.rb +120 -0
  30. data/lib/shopify_api/rest/resources/2025_01/checkout.rb +213 -0
  31. data/lib/shopify_api/rest/resources/2025_01/collect.rb +146 -0
  32. data/lib/shopify_api/rest/resources/2025_01/collection.rb +114 -0
  33. data/lib/shopify_api/rest/resources/2025_01/collection_listing.rb +159 -0
  34. data/lib/shopify_api/rest/resources/2025_01/comment.rb +287 -0
  35. data/lib/shopify_api/rest/resources/2025_01/country.rb +141 -0
  36. data/lib/shopify_api/rest/resources/2025_01/currency.rb +61 -0
  37. data/lib/shopify_api/rest/resources/2025_01/custom_collection.rb +191 -0
  38. data/lib/shopify_api/rest/resources/2025_01/customer.rb +328 -0
  39. data/lib/shopify_api/rest/resources/2025_01/customer_address.rb +215 -0
  40. data/lib/shopify_api/rest/resources/2025_01/deprecated_api_call.rb +61 -0
  41. data/lib/shopify_api/rest/resources/2025_01/discount_code.rb +226 -0
  42. data/lib/shopify_api/rest/resources/2025_01/dispute.rb +115 -0
  43. data/lib/shopify_api/rest/resources/2025_01/dispute_evidence.rb +121 -0
  44. data/lib/shopify_api/rest/resources/2025_01/dispute_file_upload.rb +85 -0
  45. data/lib/shopify_api/rest/resources/2025_01/draft_order.rb +279 -0
  46. data/lib/shopify_api/rest/resources/2025_01/event.rb +152 -0
  47. data/lib/shopify_api/rest/resources/2025_01/fulfillment.rb +235 -0
  48. data/lib/shopify_api/rest/resources/2025_01/fulfillment_event.rb +167 -0
  49. data/lib/shopify_api/rest/resources/2025_01/fulfillment_order.rb +326 -0
  50. data/lib/shopify_api/rest/resources/2025_01/fulfillment_request.rb +116 -0
  51. data/lib/shopify_api/rest/resources/2025_01/fulfillment_service.rb +134 -0
  52. data/lib/shopify_api/rest/resources/2025_01/gift_card.rb +222 -0
  53. data/lib/shopify_api/rest/resources/2025_01/gift_card_adjustment.rb +122 -0
  54. data/lib/shopify_api/rest/resources/2025_01/image.rb +161 -0
  55. data/lib/shopify_api/rest/resources/2025_01/inventory_item.rb +112 -0
  56. data/lib/shopify_api/rest/resources/2025_01/inventory_level.rb +183 -0
  57. data/lib/shopify_api/rest/resources/2025_01/location.rb +171 -0
  58. data/lib/shopify_api/rest/resources/2025_01/locations_for_move.rb +60 -0
  59. data/lib/shopify_api/rest/resources/2025_01/marketing_event.rb +213 -0
  60. data/lib/shopify_api/rest/resources/2025_01/metafield.rb +348 -0
  61. data/lib/shopify_api/rest/resources/2025_01/mobile_platform_application.rb +120 -0
  62. data/lib/shopify_api/rest/resources/2025_01/order.rb +503 -0
  63. data/lib/shopify_api/rest/resources/2025_01/order_risk.rb +148 -0
  64. data/lib/shopify_api/rest/resources/2025_01/page.rb +198 -0
  65. data/lib/shopify_api/rest/resources/2025_01/payment.rb +98 -0
  66. data/lib/shopify_api/rest/resources/2025_01/payment_gateway.rb +147 -0
  67. data/lib/shopify_api/rest/resources/2025_01/payment_transaction.rb +117 -0
  68. data/lib/shopify_api/rest/resources/2025_01/payout.rb +101 -0
  69. data/lib/shopify_api/rest/resources/2025_01/policy.rb +73 -0
  70. data/lib/shopify_api/rest/resources/2025_01/price_rule.rb +227 -0
  71. data/lib/shopify_api/rest/resources/2025_01/product.rb +227 -0
  72. data/lib/shopify_api/rest/resources/2025_01/product_listing.rb +200 -0
  73. data/lib/shopify_api/rest/resources/2025_01/product_resource_feedback.rb +92 -0
  74. data/lib/shopify_api/rest/resources/2025_01/province.rb +136 -0
  75. data/lib/shopify_api/rest/resources/2025_01/recurring_application_charge.rb +184 -0
  76. data/lib/shopify_api/rest/resources/2025_01/redirect.rb +143 -0
  77. data/lib/shopify_api/rest/resources/2025_01/refund.rb +158 -0
  78. data/lib/shopify_api/rest/resources/2025_01/resource_feedback.rb +77 -0
  79. data/lib/shopify_api/rest/resources/2025_01/script_tag.rb +159 -0
  80. data/lib/shopify_api/rest/resources/2025_01/shipping_zone.rb +87 -0
  81. data/lib/shopify_api/rest/resources/2025_01/shop.rb +231 -0
  82. data/lib/shopify_api/rest/resources/2025_01/smart_collection.rb +220 -0
  83. data/lib/shopify_api/rest/resources/2025_01/storefront_access_token.rb +91 -0
  84. data/lib/shopify_api/rest/resources/2025_01/tender_transaction.rb +97 -0
  85. data/lib/shopify_api/rest/resources/2025_01/theme.rb +127 -0
  86. data/lib/shopify_api/rest/resources/2025_01/transaction.rb +194 -0
  87. data/lib/shopify_api/rest/resources/2025_01/usage_charge.rb +106 -0
  88. data/lib/shopify_api/rest/resources/2025_01/user.rb +142 -0
  89. data/lib/shopify_api/rest/resources/2025_01/variant.rb +212 -0
  90. data/lib/shopify_api/rest/resources/2025_01/webhook.rb +173 -0
  91. data/lib/shopify_api/rest/resources/2025_04/abandoned_checkout.rb +194 -0
  92. data/lib/shopify_api/rest/resources/2025_04/access_scope.rb +62 -0
  93. data/lib/shopify_api/rest/resources/2025_04/apple_pay_certificate.rb +109 -0
  94. data/lib/shopify_api/rest/resources/2025_04/application_charge.rb +113 -0
  95. data/lib/shopify_api/rest/resources/2025_04/application_credit.rb +95 -0
  96. data/lib/shopify_api/rest/resources/2025_04/article.rb +269 -0
  97. data/lib/shopify_api/rest/resources/2025_04/asset.rb +122 -0
  98. data/lib/shopify_api/rest/resources/2025_04/assigned_fulfillment_order.rb +92 -0
  99. data/lib/shopify_api/rest/resources/2025_04/balance.rb +58 -0
  100. data/lib/shopify_api/rest/resources/2025_04/blog.rb +166 -0
  101. data/lib/shopify_api/rest/resources/2025_04/cancellation_request.rb +87 -0
  102. data/lib/shopify_api/rest/resources/2025_04/carrier_service.rb +120 -0
  103. data/lib/shopify_api/rest/resources/2025_04/checkout.rb +213 -0
  104. data/lib/shopify_api/rest/resources/2025_04/collect.rb +146 -0
  105. data/lib/shopify_api/rest/resources/2025_04/collection.rb +114 -0
  106. data/lib/shopify_api/rest/resources/2025_04/collection_listing.rb +159 -0
  107. data/lib/shopify_api/rest/resources/2025_04/comment.rb +287 -0
  108. data/lib/shopify_api/rest/resources/2025_04/country.rb +141 -0
  109. data/lib/shopify_api/rest/resources/2025_04/currency.rb +61 -0
  110. data/lib/shopify_api/rest/resources/2025_04/custom_collection.rb +191 -0
  111. data/lib/shopify_api/rest/resources/2025_04/customer.rb +328 -0
  112. data/lib/shopify_api/rest/resources/2025_04/customer_address.rb +215 -0
  113. data/lib/shopify_api/rest/resources/2025_04/deprecated_api_call.rb +61 -0
  114. data/lib/shopify_api/rest/resources/2025_04/discount_code.rb +226 -0
  115. data/lib/shopify_api/rest/resources/2025_04/dispute.rb +115 -0
  116. data/lib/shopify_api/rest/resources/2025_04/dispute_evidence.rb +121 -0
  117. data/lib/shopify_api/rest/resources/2025_04/dispute_file_upload.rb +85 -0
  118. data/lib/shopify_api/rest/resources/2025_04/draft_order.rb +279 -0
  119. data/lib/shopify_api/rest/resources/2025_04/event.rb +152 -0
  120. data/lib/shopify_api/rest/resources/2025_04/fulfillment.rb +235 -0
  121. data/lib/shopify_api/rest/resources/2025_04/fulfillment_event.rb +167 -0
  122. data/lib/shopify_api/rest/resources/2025_04/fulfillment_order.rb +326 -0
  123. data/lib/shopify_api/rest/resources/2025_04/fulfillment_request.rb +116 -0
  124. data/lib/shopify_api/rest/resources/2025_04/fulfillment_service.rb +134 -0
  125. data/lib/shopify_api/rest/resources/2025_04/gift_card.rb +222 -0
  126. data/lib/shopify_api/rest/resources/2025_04/gift_card_adjustment.rb +122 -0
  127. data/lib/shopify_api/rest/resources/2025_04/image.rb +161 -0
  128. data/lib/shopify_api/rest/resources/2025_04/inventory_item.rb +112 -0
  129. data/lib/shopify_api/rest/resources/2025_04/inventory_level.rb +183 -0
  130. data/lib/shopify_api/rest/resources/2025_04/location.rb +171 -0
  131. data/lib/shopify_api/rest/resources/2025_04/locations_for_move.rb +60 -0
  132. data/lib/shopify_api/rest/resources/2025_04/marketing_event.rb +213 -0
  133. data/lib/shopify_api/rest/resources/2025_04/metafield.rb +348 -0
  134. data/lib/shopify_api/rest/resources/2025_04/mobile_platform_application.rb +120 -0
  135. data/lib/shopify_api/rest/resources/2025_04/order.rb +503 -0
  136. data/lib/shopify_api/rest/resources/2025_04/order_risk.rb +148 -0
  137. data/lib/shopify_api/rest/resources/2025_04/page.rb +198 -0
  138. data/lib/shopify_api/rest/resources/2025_04/payment.rb +98 -0
  139. data/lib/shopify_api/rest/resources/2025_04/payment_gateway.rb +147 -0
  140. data/lib/shopify_api/rest/resources/2025_04/payment_transaction.rb +117 -0
  141. data/lib/shopify_api/rest/resources/2025_04/payout.rb +101 -0
  142. data/lib/shopify_api/rest/resources/2025_04/policy.rb +73 -0
  143. data/lib/shopify_api/rest/resources/2025_04/price_rule.rb +227 -0
  144. data/lib/shopify_api/rest/resources/2025_04/product.rb +227 -0
  145. data/lib/shopify_api/rest/resources/2025_04/product_listing.rb +200 -0
  146. data/lib/shopify_api/rest/resources/2025_04/product_resource_feedback.rb +92 -0
  147. data/lib/shopify_api/rest/resources/2025_04/province.rb +136 -0
  148. data/lib/shopify_api/rest/resources/2025_04/recurring_application_charge.rb +184 -0
  149. data/lib/shopify_api/rest/resources/2025_04/redirect.rb +143 -0
  150. data/lib/shopify_api/rest/resources/2025_04/refund.rb +158 -0
  151. data/lib/shopify_api/rest/resources/2025_04/resource_feedback.rb +77 -0
  152. data/lib/shopify_api/rest/resources/2025_04/script_tag.rb +159 -0
  153. data/lib/shopify_api/rest/resources/2025_04/shipping_zone.rb +87 -0
  154. data/lib/shopify_api/rest/resources/2025_04/shop.rb +231 -0
  155. data/lib/shopify_api/rest/resources/2025_04/smart_collection.rb +220 -0
  156. data/lib/shopify_api/rest/resources/2025_04/storefront_access_token.rb +91 -0
  157. data/lib/shopify_api/rest/resources/2025_04/tender_transaction.rb +97 -0
  158. data/lib/shopify_api/rest/resources/2025_04/theme.rb +127 -0
  159. data/lib/shopify_api/rest/resources/2025_04/transaction.rb +194 -0
  160. data/lib/shopify_api/rest/resources/2025_04/usage_charge.rb +106 -0
  161. data/lib/shopify_api/rest/resources/2025_04/user.rb +142 -0
  162. data/lib/shopify_api/rest/resources/2025_04/variant.rb +212 -0
  163. data/lib/shopify_api/rest/resources/2025_04/webhook.rb +173 -0
  164. data/lib/shopify_api/utils/session_utils.rb +1 -1
  165. data/lib/shopify_api/version.rb +1 -1
  166. metadata +151 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2594b938c059a24c4f17f5a435612a01fe880eb4ab5096f25ac0d0513129c71c
4
- data.tar.gz: 3b584fffb9213fefaf5f1a603c76de8292ecb30e7434ccc63af7265ab3377987
3
+ metadata.gz: 80e32598197d0d0becb6f4fb8175cd9f0b7ca5ad83763a5934a55ff7c8a3f769
4
+ data.tar.gz: 38f4b87f9432966ac19017991fb85f8696e0adcd6e047378b3b6c2c35e088f66
5
5
  SHA512:
6
- metadata.gz: 5e565a15428752c7838332f42d02c6aad73fe837dcc1c91774ed2cd523c81b6cd440e82dc0f2d8d65494a0d7715bb75f8533a3105304d2a510b1cd104690b1f2
7
- data.tar.gz: e3625bc9e1e17ef36162fcc8c416478dbdf9f8b7bc868fb228d07ba4c2878bedb72738765e82737da9cd59b4c5d7197ba81c15d9013a38f13987e5ba3fbdf93d
6
+ metadata.gz: 2be22dec439c86f297e71e19fa0fbc04f5bb31eb296fd0632567caee65874985b3595d4bf9e9452a09540cb76c302117413e397ba90342e590882b72fbcf7dc8
7
+ data.tar.gz: 2d22864ebcb109ba38cef3a461696d7c7b19426c4f8e75b9041b3fc48e26af957a30a1dfa02895d7f2da1e000d44bf797ad5b7529ebd5240a1d19a56a8fa60df
@@ -13,8 +13,8 @@ jobs:
13
13
  actions: 'close-issues'
14
14
  token: ${{ secrets.GITHUB_TOKEN }}
15
15
  labels: 'Waiting for Response'
16
- inactive-day: 7
16
+ inactive-day: 14
17
17
  body: |
18
- We are closing this issue because we did not hear back regarding additional details we needed to resolve this issue. If the issue persists and you are able to provide the missing clarification we need, feel free to respond and reopen this issue.
18
+ We are closing this issue because we did not hear back regarding additional details we needed to resolve this issue. If the issue persists and you are able to provide the missing clarification we need, feel free to create a new issue with the additional information.
19
19
 
20
20
  We appreciate your understanding as we try to manage our number of open issues.
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.6
1
+ 3.3.7
data/CHANGELOG.md CHANGED
@@ -3,11 +3,24 @@
3
3
  Note: For changes to the API, see https://shopify.dev/changelog?filter=api
4
4
  ## Unreleased
5
5
 
6
+ ## 14.9.0
7
+
8
+ - [#1362](https://github.com/Shopify/shopify-api-ruby/pull/1362) Add support for client credentials grant
9
+ - [#1372](https://github.com/Shopify/shopify-api-ruby/pull/1372) Add support for 2025-04 API version
10
+ - [#1369](https://github.com/Shopify/shopify-api-ruby/pull/1369) Make `sub` and `sid` jwt claims optional (Checkout ui extension support)
11
+ - [#1370](https://github.com/Shopify/shopify-api-ruby/pull/1370) Add support for Shopify internal hosts
12
+ - [#1366](https://github.com/Shopify/shopify-api-ruby/pull/1366) Add support for release candidate API versions
13
+
14
+ ## 14.8.0
15
+
16
+ - [#1355](https://github.com/Shopify/shopify-api-ruby/pull/1355) Add support for 2025-01 API version
17
+
6
18
  ## 14.7.0
7
19
 
8
20
  - [#1347](https://github.com/Shopify/shopify-api-ruby/pull/1347) Extend webhook registration to support filters
9
21
  - [#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
22
  - [#1343](https://github.com/Shopify/shopify-api-ruby/pull/1343) Make ShopifyAPI::Context::scope parameter optional. `scope` defaults to empty list `[]`.
23
+ - [#1348](https://github.com/Shopify/shopify-api-ruby/pull/1348) Add config option that will disable the REST API client and REST resources. New apps should use the GraphQL Admin API
11
24
 
12
25
  ## 14.6.0
13
26
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (14.7.0)
4
+ shopify_api (14.9.0)
5
5
  activesupport
6
6
  concurrent-ruby
7
7
  hash_diff
@@ -39,7 +39,7 @@ QUERY
39
39
 
40
40
  # You may not need the "Shopify-Storefront-Buyer-IP" header, see its documentation:
41
41
  # https://shopify.dev/docs/api/usage/authentication#making-server-side-requests
42
- response = client.query(query: query, headers: { "Shopify-Storefront-Buyer-IP": request.ip })
42
+ response = client.query(query: query, headers: { "Shopify-Storefront-Buyer-IP": request.remote_ip })
43
43
  # do something with the returned data
44
44
  ```
45
45
 
data/docs/usage/oauth.md CHANGED
@@ -11,7 +11,8 @@ For more information on authenticating a Shopify app please see the [Types of Au
11
11
  - [Note about Rails](#note-about-rails)
12
12
  - [Performing OAuth](#performing-oauth-1)
13
13
  - [Token Exchange](#token-exchange)
14
- - [Authorization Code Grant Flow](#authorization-code-grant-flow)
14
+ - [Authorization Code Grant](#authorization-code-grant)
15
+ - [Client Credentials Grant](#client-credentials-grant)
15
16
  - [Using OAuth Session to make authenticated API calls](#using-oauth-session-to-make-authenticated-api-calls)
16
17
 
17
18
  ## Session Persistence
@@ -31,10 +32,14 @@ with [token exchange](#token-exchange) instead of the authorization code grant f
31
32
  - Recommended and is only available for embedded apps
32
33
  - Doesn't require redirects, which makes authorization faster and prevents flickering when loading the app
33
34
  - Access scope changes are handled by [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
34
- 2. [Authorization Code Grant Flow](#authorization-code-grant-flow)
35
+ 2. [Authorization Code Grant](#authorization-code-grant)
35
36
  - OAuth flow that requires the app to redirect the user to Shopify for installation/authorization of the app to access the shop's data.
36
37
  - Suitable for non-embedded apps
37
38
  - Installations, and access scope changes are managed by the app
39
+ 3. [Client Credentials Grant](#client-credentials-grant)
40
+ - Suitable for apps without a UI
41
+ - Doesn't require user interaction in the browser
42
+ - Access scope changes are handled by [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
38
43
 
39
44
  ## Note about Rails
40
45
  If using in the Rails framework, we highly recommend you use the [shopify_app](https://github.com/Shopify/shopify_app) gem to perform OAuth, you won't have to follow the instructions below to start your own OAuth flow.
@@ -94,7 +99,7 @@ end
94
99
 
95
100
  ```
96
101
 
97
- ### Authorization Code Grant Flow
102
+ ### Authorization Code Grant
98
103
  ##### Steps
99
104
  1. [Add a route to start OAuth](#1-add-a-route-to-start-oauth)
100
105
  2. [Add an Oauth callback route](#2-add-an-oauth-callback-route)
@@ -265,9 +270,41 @@ def callback
265
270
  end
266
271
  end
267
272
  ```
268
-
269
273
  ⚠️ You can see a concrete example in the `ShopifyApp` gem's [CallbackController](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/callback_controller.rb).
270
274
 
275
+ ### Client Credentials Grant
276
+
277
+ > [!NOTE]
278
+ > You can only use the client credentials grant when building apps for your own organization.
279
+
280
+ > [!WARNING]
281
+ > [token exchange](#token-exchange) (for embedded apps) or the [authorization code grant](#authorization-code-grant) should be used instead of the client credentials grant, if your app is a browser based web app.
282
+
283
+ #### Perform Client Credentials Grant
284
+ Use [`ShopifyAPI::Auth::ClientCredentials`](https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/auth/client_credentials.rb) to
285
+ exchange the [app's client ID and client secret](https://shopify.dev/docs/apps/build/authentication-authorization/client-secrets) for an access token.
286
+ #### Input
287
+ | Parameter | Type | Required? | Default Value | Notes |
288
+ | -------------- | ---------------------- | :-------: | :-----------: | ----------------------------------------------------------------------------------------------------------- |
289
+ | `shop` | `String` | Yes | - | A Shopify domain name in the form `{exampleshop}.myshopify.com`. |
290
+
291
+ #### Output
292
+ This method returns the new `ShopifyAPI::Auth::Session` object from the client credentials grant, your app should store this `Session` object to be used later [when making authenticated API calls](#using-oauth-session-to-make-authenticated-api-calls).
293
+
294
+ #### Example
295
+ ```ruby
296
+
297
+ # `shop` is the shop domain name - "this-is-my-example-shop.myshopify.com"
298
+
299
+ def authenticate(shop)
300
+ session = ShopifyAPI::Auth::ClientCredentials.client_credentials(
301
+ shop: shop,
302
+ )
303
+ SessionRepository.store_session(session)
304
+ end
305
+
306
+ ```
307
+
271
308
  ## Using OAuth Session to make authenticated API calls
272
309
  Once your OAuth flow is complete, and you have persisted your `Session` object, you may use that `Session` object to make authenticated API calls.
273
310
 
data/docs/usage/rest.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # Make a REST API call
2
+ > [!WARNING]
3
+ > The Admin REST API has been deprecated. New apps should use the GraphQL Admin API. For more information see [All in on GraphQL](https://www.shopify.com/ca/partners/blog/all-in-on-graphql). New apps will be created with the config option `rest_disabled: true`. This will raise a `ShopifyAPI::Errors::DisabledResourceError` if you try to use the REST API.
2
4
 
3
5
  Once OAuth is complete, we can use `ShopifyAPI`'s REST library to make authenticated API calls to the Shopify Admin API.
4
6
  #### Required Session
@@ -5,6 +5,9 @@ module ShopifyAPI
5
5
  module AdminVersions
6
6
  SUPPORTED_ADMIN_VERSIONS = T.let([
7
7
  "unstable",
8
+ "2025-07",
9
+ "2025-04",
10
+ "2025-01",
8
11
  "2024-10",
9
12
  "2024-07",
10
13
  "2024-04",
@@ -19,9 +22,11 @@ module ShopifyAPI
19
22
  "2022-01",
20
23
  ], T::Array[String])
21
24
 
22
- LATEST_SUPPORTED_ADMIN_VERSION = T.let("2024-10", String)
25
+ LATEST_SUPPORTED_ADMIN_VERSION = T.let("2025-04", String)
26
+ RELEASE_CANDIDATE_ADMIN_VERSION = T.let("2025-07", String)
23
27
  end
24
28
 
25
29
  SUPPORTED_ADMIN_VERSIONS = ShopifyAPI::AdminVersions::SUPPORTED_ADMIN_VERSIONS
26
30
  LATEST_SUPPORTED_ADMIN_VERSION = ShopifyAPI::AdminVersions::LATEST_SUPPORTED_ADMIN_VERSION
31
+ RELEASE_CANDIDATE_ADMIN_VERSION = ShopifyAPI::AdminVersions::RELEASE_CANDIDATE_ADMIN_VERSION
27
32
  end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ShopifyAPI
5
+ module Auth
6
+ module ClientCredentials
7
+ extend T::Sig
8
+
9
+ CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"
10
+
11
+ class << self
12
+ extend T::Sig
13
+
14
+ sig do
15
+ params(
16
+ shop: String,
17
+ ).returns(ShopifyAPI::Auth::Session)
18
+ end
19
+ def client_credentials(shop:)
20
+ unless ShopifyAPI::Context.setup?
21
+ raise ShopifyAPI::Errors::ContextNotSetupError,
22
+ "ShopifyAPI::Context not setup, please call ShopifyAPI::Context.setup"
23
+ end
24
+
25
+ shop_session = ShopifyAPI::Auth::Session.new(shop: shop)
26
+ body = {
27
+ client_id: ShopifyAPI::Context.api_key,
28
+ client_secret: ShopifyAPI::Context.api_secret_key,
29
+ grant_type: CLIENT_CREDENTIALS_GRANT_TYPE,
30
+ }
31
+
32
+ client = Clients::HttpClient.new(session: shop_session, base_path: "/admin/oauth")
33
+ response =
34
+ client.request(
35
+ Clients::HttpRequest.new(
36
+ http_method: :post,
37
+ path: "access_token",
38
+ body: body,
39
+ body_type: "application/json",
40
+ ),
41
+ )
42
+ response_hash = T.cast(response.body, T::Hash[String, T.untyped]).to_h
43
+
44
+ Session.from(
45
+ shop: shop,
46
+ access_token_response: Oauth::AccessTokenResponse.from_hash(response_hash),
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -10,11 +10,14 @@ module ShopifyAPI
10
10
  JWT_EXPIRATION_LEEWAY = JWT_LEEWAY
11
11
 
12
12
  sig { returns(String) }
13
- attr_reader :iss, :dest, :aud, :sub, :jti, :sid
13
+ attr_reader :iss, :dest, :aud, :jti
14
14
 
15
15
  sig { returns(Integer) }
16
16
  attr_reader :exp, :nbf, :iat
17
17
 
18
+ sig { returns(T.nilable(String)) }
19
+ attr_reader :sub, :sid
20
+
18
21
  alias_method :expire_at, :exp
19
22
 
20
23
  sig { params(token: String).void }
@@ -30,12 +33,12 @@ module ShopifyAPI
30
33
  @iss = T.let(payload_hash["iss"], String)
31
34
  @dest = T.let(payload_hash["dest"], String)
32
35
  @aud = T.let(payload_hash["aud"], String)
33
- @sub = T.let(payload_hash["sub"], String)
36
+ @sub = T.let(payload_hash["sub"], T.nilable(String))
34
37
  @exp = T.let(payload_hash["exp"], Integer)
35
38
  @nbf = T.let(payload_hash["nbf"], Integer)
36
39
  @iat = T.let(payload_hash["iat"], Integer)
37
40
  @jti = T.let(payload_hash["jti"], String)
38
- @sid = T.let(payload_hash["sid"], String)
41
+ @sid = T.let(payload_hash["sid"], T.nilable(String))
39
42
 
40
43
  raise ShopifyAPI::Errors::InvalidJwtTokenError,
41
44
  "Session token had invalid API key" unless @aud == Context.api_key
@@ -47,19 +50,9 @@ module ShopifyAPI
47
50
  end
48
51
  alias_method :shopify_domain, :shop
49
52
 
50
- sig { returns(Integer) }
53
+ sig { returns(T.nilable(Integer)) }
51
54
  def shopify_user_id
52
- @sub.to_i
53
- end
54
-
55
- # TODO: Remove before releasing v11
56
- sig { params(shop: String).returns(T::Boolean) }
57
- def validate_shop(shop)
58
- Context.logger.warn(
59
- "Deprecation notice: ShopifyAPI::Auth::JwtPayload.validate_shop no longer checks the given shop and always " \
60
- "returns true. It will be removed in v11.",
61
- )
62
- true
55
+ @sub.to_i if user_id_sub? && admin_session_token?
63
56
  end
64
57
 
65
58
  alias_method :eql?, :==
@@ -86,6 +79,16 @@ module ShopifyAPI
86
79
  rescue JWT::DecodeError => err
87
80
  raise ShopifyAPI::Errors::InvalidJwtTokenError, "Error decoding session token: #{err.message}"
88
81
  end
82
+
83
+ sig { returns(T::Boolean) }
84
+ def admin_session_token?
85
+ @iss.end_with?("/admin")
86
+ end
87
+
88
+ sig { returns(T::Boolean) }
89
+ def user_id_sub?
90
+ @sub&.match?(/\A\d+\z/) || false
91
+ end
89
92
  end
90
93
  end
91
94
  end
@@ -46,8 +46,8 @@ module ShopifyAPI
46
46
  }
47
47
 
48
48
  query_string = URI.encode_www_form(query)
49
+ auth_route = auth_base_uri(shop) + "/oauth/authorize?#{query_string}"
49
50
 
50
- auth_route = "https://#{shop}/admin/oauth/authorize?#{query_string}"
51
51
  { auth_route: auth_route, cookie: cookie }
52
52
  end
53
53
 
@@ -106,6 +106,20 @@ module ShopifyAPI
106
106
 
107
107
  { session: session, cookie: cookie }
108
108
  end
109
+
110
+ private
111
+
112
+ sig { params(shop: String).returns(String) }
113
+ def auth_base_uri(shop)
114
+ return "https://#{shop}/admin" unless defined?(DevServer)
115
+
116
+ # For first-party apps in development only, we leverage DevServer to build the admin base URI
117
+ admin_web = T.unsafe(Object.const_get("DevServer")).new("web") # rubocop:disable Sorbet/ConstantsFromStrings
118
+ admin_host = admin_web.host!(nonstandard_host_prefix: "admin")
119
+ shop_name = shop.split(".").first
120
+
121
+ "https://#{admin_host}/store/#{shop_name}"
122
+ end
109
123
  end
110
124
  end
111
125
  end
@@ -95,13 +95,16 @@ module ShopifyAPI
95
95
 
96
96
  if is_online
97
97
  associated_user = T.must(access_token_response.associated_user)
98
- expires = Time.now + access_token_response.expires_in.to_i
99
98
  associated_user_scope = access_token_response.associated_user_scope
100
99
  id = "#{shop}_#{associated_user.id}"
101
100
  else
102
101
  id = "offline_#{shop}"
103
102
  end
104
103
 
104
+ if access_token_response.expires_in
105
+ expires = Time.now + access_token_response.expires_in.to_i
106
+ end
107
+
105
108
  new(
106
109
  id: id,
107
110
  shop: shop,
@@ -40,13 +40,17 @@ module ShopifyAPI
40
40
  headers["Content-Type"] = T.must(request.body_type) if request.body_type
41
41
  headers = headers.merge(T.must(request.extra_headers)) if request.extra_headers
42
42
 
43
+ parsed_uri = URI(request_url(request))
44
+
45
+ headers = append_first_party_development_headers(headers, parsed_uri)
46
+
43
47
  tries = 0
44
48
  response = HttpResponse.new(code: 0, headers: {}, body: "")
45
49
  while tries < request.tries
46
50
  tries += 1
47
51
  res = T.cast(HTTParty.send(
48
52
  request.http_method,
49
- request_url(request),
53
+ parsed_uri.to_s,
50
54
  headers: headers,
51
55
  query: request.query,
52
56
  body: request.body.class == Hash ? T.unsafe(request.body).to_json : request.body,
@@ -115,6 +119,27 @@ module ShopifyAPI
115
119
  end
116
120
  body.to_json
117
121
  end
122
+
123
+ private
124
+
125
+ sig do
126
+ params(
127
+ headers: T::Hash[T.any(Symbol, String), T.untyped],
128
+ parsed_uri: URI::Generic,
129
+ ).returns(T::Hash[T.any(Symbol, String), T.untyped])
130
+ end
131
+ def append_first_party_development_headers(headers, parsed_uri)
132
+ return headers unless defined?(DevServer)
133
+ return headers unless headers["Host"]&.include?(".my.shop.dev") || parsed_uri.host&.include?(".my.shop.dev")
134
+
135
+ # These headers are only used for first party applications in development mode
136
+ headers["x-forwarded-host"] = headers["Host"] || parsed_uri.host
137
+ headers["Host"] = T.unsafe(
138
+ Object.const_get("DevServer::Core"), # rubocop:disable Sorbet/ConstantsFromStrings
139
+ ).new.host!(:app)
140
+
141
+ headers
142
+ end
118
143
  end
119
144
  end
120
145
  end
@@ -9,6 +9,11 @@ module ShopifyAPI
9
9
 
10
10
  sig { params(session: T.nilable(Auth::Session), api_version: T.nilable(String)).void }
11
11
  def initialize(session: nil, api_version: nil)
12
+ if Context.rest_disabled
13
+ raise Errors::DisabledResourceError,
14
+ "The Admin REST API has been deprecated. Please use the GraphQL Admin API. For more information see https://www.shopify.com/ca/partners/blog/all-in-on-graphql"
15
+ end
16
+
12
17
  @api_version = T.let(api_version || Context.api_version, String)
13
18
  if api_version
14
19
  if api_version == Context.api_version
@@ -22,6 +22,7 @@ module ShopifyAPI
22
22
  @user_agent_prefix = T.let(nil, T.nilable(String))
23
23
  @old_api_secret_key = T.let(nil, T.nilable(String))
24
24
  @response_as_struct = T.let(false, T.nilable(T::Boolean))
25
+ @rest_disabled = T.let(false, T.nilable(T::Boolean))
25
26
 
26
27
  @rest_resource_loader = T.let(nil, T.nilable(Zeitwerk::Loader))
27
28
 
@@ -45,6 +46,7 @@ module ShopifyAPI
45
46
  old_api_secret_key: T.nilable(String),
46
47
  api_host: T.nilable(String),
47
48
  response_as_struct: T.nilable(T::Boolean),
49
+ rest_disabled: T.nilable(T::Boolean),
48
50
  ).void
49
51
  end
50
52
  def setup(
@@ -62,7 +64,8 @@ module ShopifyAPI
62
64
  user_agent_prefix: nil,
63
65
  old_api_secret_key: nil,
64
66
  api_host: nil,
65
- response_as_struct: false
67
+ response_as_struct: false,
68
+ rest_disabled: false
66
69
  )
67
70
  unless ShopifyAPI::AdminVersions::SUPPORTED_ADMIN_VERSIONS.include?(api_version)
68
71
  raise Errors::UnsupportedVersionError,
@@ -82,6 +85,7 @@ module ShopifyAPI
82
85
  @user_agent_prefix = user_agent_prefix
83
86
  @old_api_secret_key = old_api_secret_key
84
87
  @response_as_struct = response_as_struct
88
+ @rest_disabled = rest_disabled
85
89
  @log_level = if valid_log_level?(log_level)
86
90
  log_level.to_sym
87
91
  else
@@ -97,10 +101,11 @@ module ShopifyAPI
97
101
  @rest_resource_loader&.setup
98
102
  @rest_resource_loader&.unload
99
103
 
100
- # No resources for the unstable version
101
- return if api_version == "unstable"
104
+ # No resources for the unstable version or the release candidate version
105
+ return if api_version == "unstable" || api_version == RELEASE_CANDIDATE_ADMIN_VERSION
102
106
 
103
107
  version_folder_name = api_version.gsub("-", "_")
108
+
104
109
  path = "#{__dir__}/rest/resources/#{version_folder_name}"
105
110
 
106
111
  unless Dir.exist?(path)
@@ -178,6 +183,11 @@ module ShopifyAPI
178
183
  T.must(URI(T.must(host)).host)
179
184
  end
180
185
 
186
+ sig { returns(T::Boolean) }
187
+ def rest_disabled
188
+ T.must(@rest_disabled)
189
+ end
190
+
181
191
  private
182
192
 
183
193
  sig { params(log_level: T.any(Symbol, String)).returns(T::Boolean) }
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ShopifyAPI
5
+ module Errors
6
+ class DisabledResourceError < StandardError; end
7
+ end
8
+ end