shopify_api 14.8.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 (90) 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 +8 -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/lib/shopify_api/admin_versions.rb +5 -1
  9. data/lib/shopify_api/auth/client_credentials.rb +52 -0
  10. data/lib/shopify_api/auth/jwt_payload.rb +18 -15
  11. data/lib/shopify_api/auth/oauth.rb +15 -1
  12. data/lib/shopify_api/auth/session.rb +4 -1
  13. data/lib/shopify_api/clients/http_client.rb +26 -1
  14. data/lib/shopify_api/context.rb +3 -2
  15. data/lib/shopify_api/rest/resources/2025_04/abandoned_checkout.rb +194 -0
  16. data/lib/shopify_api/rest/resources/2025_04/access_scope.rb +62 -0
  17. data/lib/shopify_api/rest/resources/2025_04/apple_pay_certificate.rb +109 -0
  18. data/lib/shopify_api/rest/resources/2025_04/application_charge.rb +113 -0
  19. data/lib/shopify_api/rest/resources/2025_04/application_credit.rb +95 -0
  20. data/lib/shopify_api/rest/resources/2025_04/article.rb +269 -0
  21. data/lib/shopify_api/rest/resources/2025_04/asset.rb +122 -0
  22. data/lib/shopify_api/rest/resources/2025_04/assigned_fulfillment_order.rb +92 -0
  23. data/lib/shopify_api/rest/resources/2025_04/balance.rb +58 -0
  24. data/lib/shopify_api/rest/resources/2025_04/blog.rb +166 -0
  25. data/lib/shopify_api/rest/resources/2025_04/cancellation_request.rb +87 -0
  26. data/lib/shopify_api/rest/resources/2025_04/carrier_service.rb +120 -0
  27. data/lib/shopify_api/rest/resources/2025_04/checkout.rb +213 -0
  28. data/lib/shopify_api/rest/resources/2025_04/collect.rb +146 -0
  29. data/lib/shopify_api/rest/resources/2025_04/collection.rb +114 -0
  30. data/lib/shopify_api/rest/resources/2025_04/collection_listing.rb +159 -0
  31. data/lib/shopify_api/rest/resources/2025_04/comment.rb +287 -0
  32. data/lib/shopify_api/rest/resources/2025_04/country.rb +141 -0
  33. data/lib/shopify_api/rest/resources/2025_04/currency.rb +61 -0
  34. data/lib/shopify_api/rest/resources/2025_04/custom_collection.rb +191 -0
  35. data/lib/shopify_api/rest/resources/2025_04/customer.rb +328 -0
  36. data/lib/shopify_api/rest/resources/2025_04/customer_address.rb +215 -0
  37. data/lib/shopify_api/rest/resources/2025_04/deprecated_api_call.rb +61 -0
  38. data/lib/shopify_api/rest/resources/2025_04/discount_code.rb +226 -0
  39. data/lib/shopify_api/rest/resources/2025_04/dispute.rb +115 -0
  40. data/lib/shopify_api/rest/resources/2025_04/dispute_evidence.rb +121 -0
  41. data/lib/shopify_api/rest/resources/2025_04/dispute_file_upload.rb +85 -0
  42. data/lib/shopify_api/rest/resources/2025_04/draft_order.rb +279 -0
  43. data/lib/shopify_api/rest/resources/2025_04/event.rb +152 -0
  44. data/lib/shopify_api/rest/resources/2025_04/fulfillment.rb +235 -0
  45. data/lib/shopify_api/rest/resources/2025_04/fulfillment_event.rb +167 -0
  46. data/lib/shopify_api/rest/resources/2025_04/fulfillment_order.rb +326 -0
  47. data/lib/shopify_api/rest/resources/2025_04/fulfillment_request.rb +116 -0
  48. data/lib/shopify_api/rest/resources/2025_04/fulfillment_service.rb +134 -0
  49. data/lib/shopify_api/rest/resources/2025_04/gift_card.rb +222 -0
  50. data/lib/shopify_api/rest/resources/2025_04/gift_card_adjustment.rb +122 -0
  51. data/lib/shopify_api/rest/resources/2025_04/image.rb +161 -0
  52. data/lib/shopify_api/rest/resources/2025_04/inventory_item.rb +112 -0
  53. data/lib/shopify_api/rest/resources/2025_04/inventory_level.rb +183 -0
  54. data/lib/shopify_api/rest/resources/2025_04/location.rb +171 -0
  55. data/lib/shopify_api/rest/resources/2025_04/locations_for_move.rb +60 -0
  56. data/lib/shopify_api/rest/resources/2025_04/marketing_event.rb +213 -0
  57. data/lib/shopify_api/rest/resources/2025_04/metafield.rb +348 -0
  58. data/lib/shopify_api/rest/resources/2025_04/mobile_platform_application.rb +120 -0
  59. data/lib/shopify_api/rest/resources/2025_04/order.rb +503 -0
  60. data/lib/shopify_api/rest/resources/2025_04/order_risk.rb +148 -0
  61. data/lib/shopify_api/rest/resources/2025_04/page.rb +198 -0
  62. data/lib/shopify_api/rest/resources/2025_04/payment.rb +98 -0
  63. data/lib/shopify_api/rest/resources/2025_04/payment_gateway.rb +147 -0
  64. data/lib/shopify_api/rest/resources/2025_04/payment_transaction.rb +117 -0
  65. data/lib/shopify_api/rest/resources/2025_04/payout.rb +101 -0
  66. data/lib/shopify_api/rest/resources/2025_04/policy.rb +73 -0
  67. data/lib/shopify_api/rest/resources/2025_04/price_rule.rb +227 -0
  68. data/lib/shopify_api/rest/resources/2025_04/product.rb +227 -0
  69. data/lib/shopify_api/rest/resources/2025_04/product_listing.rb +200 -0
  70. data/lib/shopify_api/rest/resources/2025_04/product_resource_feedback.rb +92 -0
  71. data/lib/shopify_api/rest/resources/2025_04/province.rb +136 -0
  72. data/lib/shopify_api/rest/resources/2025_04/recurring_application_charge.rb +184 -0
  73. data/lib/shopify_api/rest/resources/2025_04/redirect.rb +143 -0
  74. data/lib/shopify_api/rest/resources/2025_04/refund.rb +158 -0
  75. data/lib/shopify_api/rest/resources/2025_04/resource_feedback.rb +77 -0
  76. data/lib/shopify_api/rest/resources/2025_04/script_tag.rb +159 -0
  77. data/lib/shopify_api/rest/resources/2025_04/shipping_zone.rb +87 -0
  78. data/lib/shopify_api/rest/resources/2025_04/shop.rb +231 -0
  79. data/lib/shopify_api/rest/resources/2025_04/smart_collection.rb +220 -0
  80. data/lib/shopify_api/rest/resources/2025_04/storefront_access_token.rb +91 -0
  81. data/lib/shopify_api/rest/resources/2025_04/tender_transaction.rb +97 -0
  82. data/lib/shopify_api/rest/resources/2025_04/theme.rb +127 -0
  83. data/lib/shopify_api/rest/resources/2025_04/transaction.rb +194 -0
  84. data/lib/shopify_api/rest/resources/2025_04/usage_charge.rb +106 -0
  85. data/lib/shopify_api/rest/resources/2025_04/user.rb +142 -0
  86. data/lib/shopify_api/rest/resources/2025_04/variant.rb +212 -0
  87. data/lib/shopify_api/rest/resources/2025_04/webhook.rb +173 -0
  88. data/lib/shopify_api/utils/session_utils.rb +1 -1
  89. data/lib/shopify_api/version.rb +1 -1
  90. metadata +77 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a405ff33cd5f3d90d6cc845f1108b0bd14aada9bacb25f1889ca66367ea9b6f
4
- data.tar.gz: e88aa78cd905c93b28bbf32b523fe0d45497585e5c2f21fb9967f052165cb6fc
3
+ metadata.gz: 80e32598197d0d0becb6f4fb8175cd9f0b7ca5ad83763a5934a55ff7c8a3f769
4
+ data.tar.gz: 38f4b87f9432966ac19017991fb85f8696e0adcd6e047378b3b6c2c35e088f66
5
5
  SHA512:
6
- metadata.gz: 1564080f210d17677a9d00eeb176a5ac37e45b3b02ed5cc5616948d35ea9821ec32614de7f229f93e39e76aca0257d107408bb2ab569451ef4cc36fc043dc194
7
- data.tar.gz: d853bdf2950a2ad3da7835057fc4c22329ed2f52a0654f5da9512f13bf26f737866b0f8608190e5106586cdc236b86444996f44fca7f485ee9dd82d3f80f1209
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,6 +3,14 @@
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
+
6
14
  ## 14.8.0
7
15
 
8
16
  - [#1355](https://github.com/Shopify/shopify-api-ruby/pull/1355) Add support for 2025-01 API version
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (14.8.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
 
@@ -5,6 +5,8 @@ module ShopifyAPI
5
5
  module AdminVersions
6
6
  SUPPORTED_ADMIN_VERSIONS = T.let([
7
7
  "unstable",
8
+ "2025-07",
9
+ "2025-04",
8
10
  "2025-01",
9
11
  "2024-10",
10
12
  "2024-07",
@@ -20,9 +22,11 @@ module ShopifyAPI
20
22
  "2022-01",
21
23
  ], T::Array[String])
22
24
 
23
- LATEST_SUPPORTED_ADMIN_VERSION = T.let("2025-01", String)
25
+ LATEST_SUPPORTED_ADMIN_VERSION = T.let("2025-04", String)
26
+ RELEASE_CANDIDATE_ADMIN_VERSION = T.let("2025-07", String)
24
27
  end
25
28
 
26
29
  SUPPORTED_ADMIN_VERSIONS = ShopifyAPI::AdminVersions::SUPPORTED_ADMIN_VERSIONS
27
30
  LATEST_SUPPORTED_ADMIN_VERSION = ShopifyAPI::AdminVersions::LATEST_SUPPORTED_ADMIN_VERSION
31
+ RELEASE_CANDIDATE_ADMIN_VERSION = ShopifyAPI::AdminVersions::RELEASE_CANDIDATE_ADMIN_VERSION
28
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
@@ -101,10 +101,11 @@ module ShopifyAPI
101
101
  @rest_resource_loader&.setup
102
102
  @rest_resource_loader&.unload
103
103
 
104
- # No resources for the unstable version
105
- 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
106
106
 
107
107
  version_folder_name = api_version.gsub("-", "_")
108
+
108
109
  path = "#{__dir__}/rest/resources/#{version_folder_name}"
109
110
 
110
111
  unless Dir.exist?(path)
@@ -0,0 +1,194 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ ########################################################################################################################
5
+ # This file is auto-generated. If you have an issue, please create a GitHub issue. #
6
+ ########################################################################################################################
7
+
8
+ module ShopifyAPI
9
+ class AbandonedCheckout < ShopifyAPI::Rest::Base
10
+ extend T::Sig
11
+
12
+ @prev_page_info = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
13
+ @next_page_info = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
14
+
15
+ @api_call_limit = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
16
+ @retry_request_after = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
17
+
18
+ sig { params(session: T.nilable(ShopifyAPI::Auth::Session), from_hash: T.nilable(T::Hash[T.untyped, T.untyped])).void }
19
+ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
20
+
21
+ @abandoned_checkout_url = T.let(nil, T.nilable(String))
22
+ @billing_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
23
+ @buyer_accepts_marketing = T.let(nil, T.nilable(T::Boolean))
24
+ @buyer_accepts_sms_marketing = T.let(nil, T.nilable(T::Boolean))
25
+ @cart_token = T.let(nil, T.nilable(String))
26
+ @closed_at = T.let(nil, T.nilable(String))
27
+ @completed_at = T.let(nil, T.nilable(String))
28
+ @created_at = T.let(nil, T.nilable(String))
29
+ @currency = T.let(nil, T.nilable(Currency))
30
+ @customer = T.let(nil, T.nilable(Customer))
31
+ @customer_locale = T.let(nil, T.nilable(String))
32
+ @device_id = T.let(nil, T.nilable(Integer))
33
+ @discount_codes = T.let(nil, T.nilable(T::Array[T.untyped]))
34
+ @email = T.let(nil, T.nilable(String))
35
+ @gateway = T.let(nil, T.nilable(String))
36
+ @id = T.let(nil, T.nilable(Integer))
37
+ @landing_site = T.let(nil, T.nilable(String))
38
+ @line_items = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
39
+ @location_id = T.let(nil, T.nilable(Integer))
40
+ @note = T.let(nil, T.nilable(String))
41
+ @phone = T.let(nil, T.nilable(String))
42
+ @presentment_currency = T.let(nil, T.nilable(String))
43
+ @referring_site = T.let(nil, T.nilable(String))
44
+ @shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
45
+ @shipping_lines = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
46
+ @sms_marketing_phone = T.let(nil, T.nilable(String))
47
+ @source_name = T.let(nil, T.nilable(String))
48
+ @subtotal_price = T.let(nil, T.nilable(String))
49
+ @tax_lines = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
50
+ @taxes_included = T.let(nil, T.nilable(T::Boolean))
51
+ @token = T.let(nil, T.nilable(String))
52
+ @total_discounts = T.let(nil, T.nilable(String))
53
+ @total_duties = T.let(nil, T.nilable(String))
54
+ @total_line_items_price = T.let(nil, T.nilable(String))
55
+ @total_price = T.let(nil, T.nilable(String))
56
+ @total_tax = T.let(nil, T.nilable(String))
57
+ @total_weight = T.let(nil, T.nilable(Integer))
58
+ @updated_at = T.let(nil, T.nilable(String))
59
+ @user_id = T.let(nil, T.nilable(Integer))
60
+
61
+ super(session: session, from_hash: from_hash)
62
+ end
63
+
64
+ @has_one = T.let({
65
+ currency: Currency,
66
+ customer: Customer
67
+ }, T::Hash[Symbol, Class])
68
+ @has_many = T.let({
69
+ discount_codes: DiscountCode
70
+ }, T::Hash[Symbol, Class])
71
+ @paths = T.let([
72
+ {http_method: :get, operation: :checkouts, ids: [], path: "checkouts.json"},
73
+ {http_method: :get, operation: :checkouts, ids: [], path: "checkouts.json"}
74
+ ], T::Array[T::Hash[String, T.any(T::Array[Symbol], String, Symbol)]])
75
+
76
+ sig { returns(T.nilable(String)) }
77
+ attr_reader :abandoned_checkout_url
78
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
79
+ attr_reader :billing_address
80
+ sig { returns(T.nilable(T::Boolean)) }
81
+ attr_reader :buyer_accepts_marketing
82
+ sig { returns(T.nilable(T::Boolean)) }
83
+ attr_reader :buyer_accepts_sms_marketing
84
+ sig { returns(T.nilable(String)) }
85
+ attr_reader :cart_token
86
+ sig { returns(T.nilable(String)) }
87
+ attr_reader :closed_at
88
+ sig { returns(T.nilable(String)) }
89
+ attr_reader :completed_at
90
+ sig { returns(T.nilable(String)) }
91
+ attr_reader :created_at
92
+ sig { returns(T.nilable(Currency)) }
93
+ attr_reader :currency
94
+ sig { returns(T.nilable(Customer)) }
95
+ attr_reader :customer
96
+ sig { returns(T.nilable(String)) }
97
+ attr_reader :customer_locale
98
+ sig { returns(T.nilable(Integer)) }
99
+ attr_reader :device_id
100
+ sig { returns(T.nilable(T::Array[DiscountCode])) }
101
+ attr_reader :discount_codes
102
+ sig { returns(T.nilable(String)) }
103
+ attr_reader :email
104
+ sig { returns(T.nilable(String)) }
105
+ attr_reader :gateway
106
+ sig { returns(T.nilable(Integer)) }
107
+ attr_reader :id
108
+ sig { returns(T.nilable(String)) }
109
+ attr_reader :landing_site
110
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
111
+ attr_reader :line_items
112
+ sig { returns(T.nilable(Integer)) }
113
+ attr_reader :location_id
114
+ sig { returns(T.nilable(String)) }
115
+ attr_reader :note
116
+ sig { returns(T.nilable(String)) }
117
+ attr_reader :phone
118
+ sig { returns(T.nilable(String)) }
119
+ attr_reader :presentment_currency
120
+ sig { returns(T.nilable(String)) }
121
+ attr_reader :referring_site
122
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
123
+ attr_reader :shipping_address
124
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
125
+ attr_reader :shipping_lines
126
+ sig { returns(T.nilable(String)) }
127
+ attr_reader :sms_marketing_phone
128
+ sig { returns(T.nilable(String)) }
129
+ attr_reader :source_name
130
+ sig { returns(T.nilable(String)) }
131
+ attr_reader :subtotal_price
132
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
133
+ attr_reader :tax_lines
134
+ sig { returns(T.nilable(T::Boolean)) }
135
+ attr_reader :taxes_included
136
+ sig { returns(T.nilable(String)) }
137
+ attr_reader :token
138
+ sig { returns(T.nilable(String)) }
139
+ attr_reader :total_discounts
140
+ sig { returns(T.nilable(String)) }
141
+ attr_reader :total_duties
142
+ sig { returns(T.nilable(String)) }
143
+ attr_reader :total_line_items_price
144
+ sig { returns(T.nilable(String)) }
145
+ attr_reader :total_price
146
+ sig { returns(T.nilable(String)) }
147
+ attr_reader :total_tax
148
+ sig { returns(T.nilable(Integer)) }
149
+ attr_reader :total_weight
150
+ sig { returns(T.nilable(String)) }
151
+ attr_reader :updated_at
152
+ sig { returns(T.nilable(Integer)) }
153
+ attr_reader :user_id
154
+
155
+ class << self
156
+ sig do
157
+ params(
158
+ since_id: T.untyped,
159
+ created_at_min: T.untyped,
160
+ created_at_max: T.untyped,
161
+ updated_at_min: T.untyped,
162
+ updated_at_max: T.untyped,
163
+ status: T.untyped,
164
+ limit: T.untyped,
165
+ session: Auth::Session,
166
+ kwargs: T.untyped
167
+ ).returns(T.untyped)
168
+ end
169
+ def checkouts(
170
+ since_id: nil,
171
+ created_at_min: nil,
172
+ created_at_max: nil,
173
+ updated_at_min: nil,
174
+ updated_at_max: nil,
175
+ status: nil,
176
+ limit: nil,
177
+ session: ShopifyAPI::Context.active_session,
178
+ **kwargs
179
+ )
180
+ request(
181
+ http_method: :get,
182
+ operation: :checkouts,
183
+ session: session,
184
+ ids: {},
185
+ params: {since_id: since_id, created_at_min: created_at_min, created_at_max: created_at_max, updated_at_min: updated_at_min, updated_at_max: updated_at_max, status: status, limit: limit}.merge(kwargs).compact,
186
+ body: {},
187
+ entity: nil,
188
+ )
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+ end
@@ -0,0 +1,62 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ ########################################################################################################################
5
+ # This file is auto-generated. If you have an issue, please create a GitHub issue. #
6
+ ########################################################################################################################
7
+
8
+ module ShopifyAPI
9
+ class AccessScope < ShopifyAPI::Rest::Base
10
+ extend T::Sig
11
+
12
+ @prev_page_info = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
13
+ @next_page_info = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
14
+
15
+ @api_call_limit = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
16
+ @retry_request_after = T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)
17
+
18
+ sig { params(session: T.nilable(ShopifyAPI::Auth::Session), from_hash: T.nilable(T::Hash[T.untyped, T.untyped])).void }
19
+ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
20
+
21
+ @handle = T.let(nil, T.nilable(String))
22
+ @access_scopes = T.let(nil, T.nilable(T::Array[T.untyped]))
23
+
24
+ super(session: session, from_hash: from_hash)
25
+ end
26
+
27
+ @has_one = T.let({}, T::Hash[Symbol, Class])
28
+ @has_many = T.let({}, T::Hash[Symbol, Class])
29
+ @custom_prefix = T.let("/admin/oauth", T.nilable(String))
30
+ @paths = T.let([
31
+ {http_method: :get, operation: :get, ids: [], path: "access_scopes.json"}
32
+ ], T::Array[T::Hash[String, T.any(T::Array[Symbol], String, Symbol)]])
33
+
34
+ sig { returns(T.nilable(String)) }
35
+ attr_reader :handle
36
+ sig { returns(T.nilable(T::Array[T::Hash[T.untyped, T.untyped]])) }
37
+ attr_reader :access_scopes
38
+
39
+ class << self
40
+ sig do
41
+ params(
42
+ session: Auth::Session,
43
+ kwargs: T.untyped
44
+ ).returns(T::Array[AccessScope])
45
+ end
46
+ def all(
47
+ session: ShopifyAPI::Context.active_session,
48
+ **kwargs
49
+ )
50
+ response = base_find(
51
+ session: session,
52
+ ids: {},
53
+ params: {}.merge(kwargs).compact,
54
+ )
55
+
56
+ T.cast(response, T::Array[AccessScope])
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end