shopify_api 14.3.0 → 14.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/BREAKING_CHANGES_FOR_V15.md +42 -0
  3. data/CHANGELOG.md +19 -0
  4. data/Gemfile.lock +2 -2
  5. data/README.md +3 -0
  6. data/docs/usage/custom_apps.md +32 -1
  7. data/docs/usage/oauth.md +88 -16
  8. data/docs/usage/webhooks.md +3 -2
  9. data/lib/shopify_api/admin_versions.rb +2 -1
  10. data/lib/shopify_api/clients/graphql/client.rb +14 -3
  11. data/lib/shopify_api/clients/graphql/storefront.rb +12 -2
  12. data/lib/shopify_api/rest/base.rb +7 -1
  13. data/lib/shopify_api/rest/resources/2022_04/customer.rb +1 -0
  14. data/lib/shopify_api/rest/resources/2022_04/recurring_application_charge.rb +9 -0
  15. data/lib/shopify_api/rest/resources/2022_04/shop.rb +12 -2
  16. data/lib/shopify_api/rest/resources/2022_07/customer.rb +1 -0
  17. data/lib/shopify_api/rest/resources/2022_07/recurring_application_charge.rb +9 -0
  18. data/lib/shopify_api/rest/resources/2022_07/shop.rb +12 -2
  19. data/lib/shopify_api/rest/resources/2022_10/customer.rb +1 -0
  20. data/lib/shopify_api/rest/resources/2022_10/recurring_application_charge.rb +9 -0
  21. data/lib/shopify_api/rest/resources/2022_10/shop.rb +12 -2
  22. data/lib/shopify_api/rest/resources/2023_01/customer.rb +1 -0
  23. data/lib/shopify_api/rest/resources/2023_01/recurring_application_charge.rb +9 -0
  24. data/lib/shopify_api/rest/resources/2023_01/shop.rb +12 -2
  25. data/lib/shopify_api/rest/resources/2023_04/customer.rb +1 -0
  26. data/lib/shopify_api/rest/resources/2023_04/recurring_application_charge.rb +9 -0
  27. data/lib/shopify_api/rest/resources/2023_04/shop.rb +12 -2
  28. data/lib/shopify_api/rest/resources/2023_07/customer.rb +1 -0
  29. data/lib/shopify_api/rest/resources/2023_07/recurring_application_charge.rb +9 -0
  30. data/lib/shopify_api/rest/resources/2023_07/shop.rb +12 -2
  31. data/lib/shopify_api/rest/resources/2023_10/customer.rb +1 -0
  32. data/lib/shopify_api/rest/resources/2023_10/recurring_application_charge.rb +9 -0
  33. data/lib/shopify_api/rest/resources/2023_10/shop.rb +12 -2
  34. data/lib/shopify_api/rest/resources/2024_01/customer.rb +1 -0
  35. data/lib/shopify_api/rest/resources/2024_01/recurring_application_charge.rb +9 -0
  36. data/lib/shopify_api/rest/resources/2024_01/shop.rb +12 -2
  37. data/lib/shopify_api/rest/resources/2024_04/customer.rb +1 -0
  38. data/lib/shopify_api/rest/resources/2024_04/recurring_application_charge.rb +9 -0
  39. data/lib/shopify_api/rest/resources/2024_04/shop.rb +12 -2
  40. data/lib/shopify_api/rest/resources/2024_07/abandoned_checkout.rb +194 -0
  41. data/lib/shopify_api/rest/resources/2024_07/access_scope.rb +62 -0
  42. data/lib/shopify_api/rest/resources/2024_07/apple_pay_certificate.rb +109 -0
  43. data/lib/shopify_api/rest/resources/2024_07/application_charge.rb +113 -0
  44. data/lib/shopify_api/rest/resources/2024_07/application_credit.rb +95 -0
  45. data/lib/shopify_api/rest/resources/2024_07/article.rb +269 -0
  46. data/lib/shopify_api/rest/resources/2024_07/asset.rb +122 -0
  47. data/lib/shopify_api/rest/resources/2024_07/assigned_fulfillment_order.rb +92 -0
  48. data/lib/shopify_api/rest/resources/2024_07/balance.rb +58 -0
  49. data/lib/shopify_api/rest/resources/2024_07/blog.rb +166 -0
  50. data/lib/shopify_api/rest/resources/2024_07/cancellation_request.rb +87 -0
  51. data/lib/shopify_api/rest/resources/2024_07/carrier_service.rb +120 -0
  52. data/lib/shopify_api/rest/resources/2024_07/checkout.rb +213 -0
  53. data/lib/shopify_api/rest/resources/2024_07/collect.rb +146 -0
  54. data/lib/shopify_api/rest/resources/2024_07/collection.rb +114 -0
  55. data/lib/shopify_api/rest/resources/2024_07/collection_listing.rb +159 -0
  56. data/lib/shopify_api/rest/resources/2024_07/comment.rb +287 -0
  57. data/lib/shopify_api/rest/resources/2024_07/country.rb +141 -0
  58. data/lib/shopify_api/rest/resources/2024_07/currency.rb +61 -0
  59. data/lib/shopify_api/rest/resources/2024_07/custom_collection.rb +191 -0
  60. data/lib/shopify_api/rest/resources/2024_07/customer.rb +335 -0
  61. data/lib/shopify_api/rest/resources/2024_07/customer_address.rb +215 -0
  62. data/lib/shopify_api/rest/resources/2024_07/deprecated_api_call.rb +61 -0
  63. data/lib/shopify_api/rest/resources/2024_07/discount_code.rb +226 -0
  64. data/lib/shopify_api/rest/resources/2024_07/dispute.rb +115 -0
  65. data/lib/shopify_api/rest/resources/2024_07/dispute_evidence.rb +121 -0
  66. data/lib/shopify_api/rest/resources/2024_07/dispute_file_upload.rb +85 -0
  67. data/lib/shopify_api/rest/resources/2024_07/draft_order.rb +279 -0
  68. data/lib/shopify_api/rest/resources/2024_07/event.rb +152 -0
  69. data/lib/shopify_api/rest/resources/2024_07/fulfillment.rb +235 -0
  70. data/lib/shopify_api/rest/resources/2024_07/fulfillment_event.rb +167 -0
  71. data/lib/shopify_api/rest/resources/2024_07/fulfillment_order.rb +326 -0
  72. data/lib/shopify_api/rest/resources/2024_07/fulfillment_request.rb +101 -0
  73. data/lib/shopify_api/rest/resources/2024_07/fulfillment_service.rb +134 -0
  74. data/lib/shopify_api/rest/resources/2024_07/gift_card.rb +222 -0
  75. data/lib/shopify_api/rest/resources/2024_07/gift_card_adjustment.rb +122 -0
  76. data/lib/shopify_api/rest/resources/2024_07/image.rb +161 -0
  77. data/lib/shopify_api/rest/resources/2024_07/inventory_item.rb +112 -0
  78. data/lib/shopify_api/rest/resources/2024_07/inventory_level.rb +183 -0
  79. data/lib/shopify_api/rest/resources/2024_07/location.rb +171 -0
  80. data/lib/shopify_api/rest/resources/2024_07/locations_for_move.rb +60 -0
  81. data/lib/shopify_api/rest/resources/2024_07/marketing_event.rb +213 -0
  82. data/lib/shopify_api/rest/resources/2024_07/metafield.rb +348 -0
  83. data/lib/shopify_api/rest/resources/2024_07/mobile_platform_application.rb +114 -0
  84. data/lib/shopify_api/rest/resources/2024_07/order.rb +489 -0
  85. data/lib/shopify_api/rest/resources/2024_07/order_risk.rb +148 -0
  86. data/lib/shopify_api/rest/resources/2024_07/page.rb +198 -0
  87. data/lib/shopify_api/rest/resources/2024_07/payment.rb +144 -0
  88. data/lib/shopify_api/rest/resources/2024_07/payment_gateway.rb +147 -0
  89. data/lib/shopify_api/rest/resources/2024_07/payment_transaction.rb +114 -0
  90. data/lib/shopify_api/rest/resources/2024_07/payout.rb +101 -0
  91. data/lib/shopify_api/rest/resources/2024_07/policy.rb +73 -0
  92. data/lib/shopify_api/rest/resources/2024_07/price_rule.rb +227 -0
  93. data/lib/shopify_api/rest/resources/2024_07/product.rb +227 -0
  94. data/lib/shopify_api/rest/resources/2024_07/product_listing.rb +200 -0
  95. data/lib/shopify_api/rest/resources/2024_07/product_resource_feedback.rb +92 -0
  96. data/lib/shopify_api/rest/resources/2024_07/province.rb +136 -0
  97. data/lib/shopify_api/rest/resources/2024_07/recurring_application_charge.rb +184 -0
  98. data/lib/shopify_api/rest/resources/2024_07/redirect.rb +143 -0
  99. data/lib/shopify_api/rest/resources/2024_07/refund.rb +155 -0
  100. data/lib/shopify_api/rest/resources/2024_07/resource_feedback.rb +77 -0
  101. data/lib/shopify_api/rest/resources/2024_07/script_tag.rb +159 -0
  102. data/lib/shopify_api/rest/resources/2024_07/shipping_zone.rb +87 -0
  103. data/lib/shopify_api/rest/resources/2024_07/shop.rb +232 -0
  104. data/lib/shopify_api/rest/resources/2024_07/smart_collection.rb +220 -0
  105. data/lib/shopify_api/rest/resources/2024_07/storefront_access_token.rb +91 -0
  106. data/lib/shopify_api/rest/resources/2024_07/tender_transaction.rb +97 -0
  107. data/lib/shopify_api/rest/resources/2024_07/theme.rb +127 -0
  108. data/lib/shopify_api/rest/resources/2024_07/transaction.rb +188 -0
  109. data/lib/shopify_api/rest/resources/2024_07/usage_charge.rb +106 -0
  110. data/lib/shopify_api/rest/resources/2024_07/user.rb +142 -0
  111. data/lib/shopify_api/rest/resources/2024_07/variant.rb +212 -0
  112. data/lib/shopify_api/rest/resources/2024_07/webhook.rb +172 -0
  113. data/lib/shopify_api/utils/attributes_comparator.rb +21 -4
  114. data/lib/shopify_api/version.rb +1 -1
  115. data/lib/shopify_api/webhooks/registry.rb +5 -4
  116. data/sorbet/rbi/shims/hash.rb +3 -0
  117. metadata +81 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f2fd17eb9b42d6054e8b676c5d446cbc9890b7156f5c4b402a5efb776ad1421
4
- data.tar.gz: 1deefc0fdcc79f57b70fb1fbeb2072916847b8d68c614b4b150dafd86f0ff6d5
3
+ metadata.gz: 8b9b61bc6316148fcd2d905384def067e31b4fca5265bb1327a7502020ee6104
4
+ data.tar.gz: b3971fadf5e0159a68953fee0883be0a6fb5f366d0a25f39201cc6c134caa0bb
5
5
  SHA512:
6
- metadata.gz: 3cfdd1a407afb83e8557d607c65dd74322a7ca944fe58b2b42d68a911917442f51c78a0a7e59759a4fca59ae063c7af253ad8a297614a409621c52e0baf60ee6
7
- data.tar.gz: e218a6a5c77d9de8e3b0a755c0bcb1b4941bb5cfba503564ffa4ca0a59c19b76e8c0cb56cb1ee458091bd43004fa3b16c200fa4696b058a0b90c9c3475bd37d0
6
+ metadata.gz: 845bcdfac189ab0b17a07540c7cad3ba3621bbc83622d16b32a143f37eaaf9909499beb8791a2f0a0b6726e8283f833c5f1702ae804bbe38393ee5d6fa0fef67
7
+ data.tar.gz: f0216976a29a1b59ab8bf89316d3ccc9b3cbda4a42b81490cb5e25af19d9cf2b2b196484397816decdde75065f387e1f9bfddd5d82a42c110be8f13654c6f8d9
@@ -0,0 +1,42 @@
1
+ # Breaking change notice for version 15.0.0
2
+
3
+ ## Removal of `ShopifyAPI::Webhooks::Handler`
4
+
5
+ The `ShopifyAPI::Webhooks::Handler` class has been removed in favor of `ShopifyAPI::Webhooks::WebhookHandler`. The `ShopifyAPI::Webhooks::WebhookHandler` class is now the recommended way to handle webhooks.
6
+
7
+ 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`.
8
+
9
+ In v14, adding new fields to the callback would become a breaking change. To make this code more flexible, handlers will now receive an object that can be typed and extended.
10
+
11
+ `data` will have the following keys
12
+ - `topic`, `String` - The topic of the webhook
13
+ - `shop`, `String` - The shop domain of the webhook
14
+ - `body`, `T::Hash[String, T.untyped]`- The body of the webhook
15
+ - `webhook_id`, `String` - The id of the webhook event to [avoid duplicates](https://shopify.dev/docs/apps/webhooks/best-practices#ignore-duplicates)
16
+ - `api_version`, `String` - The api version of the webhook
17
+
18
+ ### New implementation
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
+ ### Previous implementation
32
+ ```ruby
33
+ module WebhookHandler
34
+ include ShopifyAPI::Webhooks::Handler
35
+
36
+ class << self
37
+ def handle(topic:, shop:, body:)
38
+ puts "Received webhook! topic: #{topic} shop: #{shop} body: #{body}"
39
+ end
40
+ end
41
+ end
42
+ ```
data/CHANGELOG.md CHANGED
@@ -4,8 +4,22 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 14.5.0
8
+
9
+ - [#1327](https://github.com/Shopify/shopify-api-ruby/pull/1327) Support `?debug=true` parameter in GraphQL client requests
10
+ - [#1308](https://github.com/Shopify/shopify-api-ruby/pull/1308) Support hash_with_indifferent_access when creating REST objects from Shopify responses. Closes #1296
11
+ - [#1332](https://github.com/Shopify/shopify-api-ruby/pull/1332) Fixed an issue where `Customer` REST API PUT requests didn't send all of the fields in the `email_marketing_consent` attribute
12
+ - [#1335](https://github.com/Shopify/shopify-api-ruby/pull/1335) Add back the `current` methods for `Shop` and `RecurringApplicationCharge` resources
13
+
14
+ ## 14.4.0
15
+
16
+ - [#1325](https://github.com/Shopify/shopify-api-ruby/pull/1325) Add support for 2024-07 API version
17
+ - [#1320](https://github.com/Shopify/shopify-api-ruby/pull/1320) Fix sorbet type on Shop.tax_shipping field
18
+
7
19
  ## 14.3.0
20
+
8
21
  - [#1312](https://github.com/Shopify/shopify-api-ruby/pull/1312) Use same leeway for `exp` and `nbf` when parsing JWT
22
+ - [#1313](https://github.com/Shopify/shopify-api-ruby/pull/1313) Fix: Webhook Registry now working with response_as_struct enabled
9
23
  - [#1314](https://github.com/Shopify/shopify-api-ruby/pull/1314)
10
24
  - Add new session util method `SessionUtils::session_id_from_shopify_id_token`
11
25
  - `SessionUtils::current_session_id` now accepts shopify Id token in the format of `Bearer this_token` or just `this_token`
@@ -15,9 +29,11 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
15
29
  - `expires_at` alias for `exp` - returns the expiration time
16
30
 
17
31
  ## 14.2.0
32
+
18
33
  - [#1309](https://github.com/Shopify/shopify-api-ruby/pull/1309) Add `Session#copy_attributes_from` method
19
34
 
20
35
  ## 14.1.0
36
+
21
37
  - [#1071](https://github.com/Shopify/shopify-api-ruby/issues/1071) Fix FulfillmentEvent class types
22
38
  - Fix: InventoryItem class `harmonized_system_code` attribute type which can be either integer, string or nil
23
39
  - Fix: Variant class `inventory_quantity` attribute type which can be either integer, string or nil
@@ -26,10 +42,12 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
26
42
  - [1305](https://github.com/Shopify/shopify-api-ruby/pull/1305/) Adds support for the `2024-04` API version.
27
43
 
28
44
  ## 14.0.1
45
+
29
46
  - [#1288](https://github.com/Shopify/shopify-api-ruby/pull/1288) Fix FeatureDeprecatedError being raised without a message.
30
47
  - [1290](https://github.com/Shopify/shopify-api-ruby/pull/1290) Move deprecation of `ShopifyAPI::Webhooks::Handler#handle` to version 15.0.0
31
48
 
32
49
  ## 14.0.0
50
+
33
51
  - [#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
34
52
  - [#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.
35
53
  - [#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.
@@ -37,6 +55,7 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
37
55
  - [#1275](https://github.com/Shopify/shopify-api-ruby/pull/1275) Allow adding custom headers in REST Resource HTTP calls.
38
56
 
39
57
  ## 13.4.0
58
+
40
59
  - [#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.
41
60
  - [#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.
42
61
  - [#1257](https://github.com/Shopify/shopify-api-ruby/pull/1257) Added support for the 2024-01 API version. This also includes a fix for the `for_hash` option when creating resources.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (14.3.0)
4
+ shopify_api (14.5.0)
5
5
  activesupport
6
6
  concurrent-ruby
7
7
  hash_diff
@@ -160,4 +160,4 @@ DEPENDENCIES
160
160
  webmock
161
161
 
162
162
  BUNDLED WITH
163
- 2.3.4
163
+ 2.5.9
data/README.md CHANGED
@@ -79,6 +79,9 @@ Once your app can perform OAuth, it can now make authenticated Shopify API calls
79
79
 
80
80
  ## Breaking Change Notices
81
81
 
82
+ ### Breaking change notice for version 15.0.0
83
+ See [BREAKING_CHANGES_FOR_V15](BREAKING_CHANGES_FOR_V15.md)
84
+
82
85
  ### Breaking change notice for version 10.0.0
83
86
  See [BREAKING_CHANGES_FOR_V10](BREAKING_CHANGES_FOR_V10.md)
84
87
 
@@ -53,6 +53,15 @@ def configure_app
53
53
  access_token: "the_token_for_your_custom_app_found_in_admin"
54
54
  )
55
55
 
56
+ ShopifyAPI::Context.setup(
57
+ api_key: "<api-key>",
58
+ api_secret_key: "<api-secret-key>",
59
+ scope: "read_orders,read_products,etc",
60
+ is_embedded: true, # Set to true if you are building an embedded app
61
+ api_version: "2024-01", # The version of the API you would like to use
62
+ is_private: true, # Set to true if you have an existing private app
63
+ )
64
+
56
65
  # Activate session to be used in all API calls
57
66
  # session must be type `ShopifyAPI::Auth::Session`
58
67
  ShopifyAPI::Context.activate_session(session)
@@ -62,9 +71,31 @@ end
62
71
  def make_api_request
63
72
  # 1. Create API client without session information
64
73
  # The graphql_client will use `ShopifyAPI::Context.active_session` when making API calls
65
- graphql_client = ShopifyAPI::Clients::Graphql::Admin.new
74
+ # you can set the api version for your GraphQL client to override the api version in ShopifyAPI::Context
75
+ graphql_client = ShopifyAPI::Clients::Graphql::Admin.new(api_version: "2024-07")
66
76
 
67
77
  # 2. Use API client to make queries
78
+ # Graphql
79
+ query = <<~QUERY
80
+ {
81
+ products(first: 10) {
82
+ edges {
83
+ cursor
84
+ node {
85
+ id
86
+ title
87
+ onlineStoreUrl
88
+ }
89
+ }
90
+ }
91
+ }
92
+ QUERY
93
+
94
+ response = graphql_client.query(query: query)
95
+
96
+ # Use REST resources to make authenticated API call
97
+ product_count = ShopifyAPI::Product.count
98
+
68
99
  ...
69
100
  end
70
101
 
data/docs/usage/oauth.md CHANGED
@@ -5,31 +5,103 @@ Once the library is set up for your project, you'll be able to use it to start a
5
5
  To do this, you can follow the steps below.
6
6
  For more information on authenticating a Shopify app please see the [Types of Authentication](https://shopify.dev/docs/apps/auth#types-of-authentication) page.
7
7
 
8
+ #### Table of contents
9
+ - [Session Persistence](#session-persistence)
10
+ - [Supported types of OAuth Flow](#supported-types-of-oauth)
11
+ - [Note about Rails](#note-about-rails)
12
+ - [Performing OAuth](#performing-oauth-1)
13
+ - [Token Exchange](#token-exchange)
14
+ - [Authorization Code Grant Flow](#authorization-code-grant-flow)
15
+ - [Using OAuth Session to make authenticated API calls](#using-oauth-session-to-make-authenticated-api-calls)
16
+
8
17
  ## Session Persistence
9
18
  Session persistence is deprecated from the `ShopifyAPI` library gem since [version 12.3.0](https://github.com/Shopify/shopify-api-ruby/blob/main/CHANGELOG.md#version-1230). The responsibility of session storage typically is fulfilled by the web framework middleware.
10
19
  This API library's focus is on making requests and facilitate session creation.
11
20
 
12
21
  ⚠️ If you're not using the [ShopifyApp](https://github.com/Shopify/shopify_app) gem, you may use ShopifyAPI to perform OAuth to create sessions, but you must implement your own session storage method to persist the session information to be used in authenticated API calls.
13
22
 
23
+ ## Supported Types of OAuth
24
+ > [!TIP]
25
+ > If you are building an embedded app, we **strongly** recommend using [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
26
+ with [token exchange](#token-exchange) instead of the authorization code grant flow.
27
+
28
+ 1. [Token Exchange](#token-exchange)
29
+ - OAuth flow by exchanging the current user's [session token (shopify id token)](https://shopify.dev/docs/apps/auth/session-tokens) for an
30
+ [access token](https://shopify.dev/docs/apps/auth/access-token-types/online.md).
31
+ - Recommended and is only available for embedded apps
32
+ - Doesn't require redirects, which makes authorization faster and prevents flickering when loading the app
33
+ - 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
+ - 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
+ - Suitable for non-embedded apps
37
+ - Installations, and access scope changes are managed by the app
38
+
14
39
  ## Note about Rails
15
40
  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.
16
41
  - See `ShopifyApp`'s [documentation on session storage](https://github.com/Shopify/shopify_app/blob/main/docs/shopify_app/sessions.md#sessions)
17
42
 
18
43
  If you aren't using Rails, you can look at how the `ShopifyApp` gem handles OAuth flow for further examples:
19
- - [Session Controller](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/sessions_controller.rb)
20
- - Triggering and redirecting user to **begin** OAuth flow
21
- - [Callback Controller](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/callback_controller.rb)
22
- - Creating / storing sessions to **complete** the OAuth flow
44
+ - Token Exchange Flow
45
+ - [Token Exchange](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/auth/token_exchange.rb)
46
+ - Completes token exchange flow to get online and offline access tokens
47
+ - Authorization Code Grant Flow
48
+ - [Session Controller](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/sessions_controller.rb)
49
+ - Triggering and redirecting user to **begin** OAuth flow
50
+ - [Callback Controller](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/callback_controller.rb)
51
+ - Creating / storing sessions to **complete** the OAuth flow
23
52
 
24
53
  ## Performing OAuth
54
+ ### Token Exchange
25
55
  #### Steps
56
+ 1. Enable [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
57
+ by configuring your scopes [through the Shopify CLI](https://shopify.dev/docs/apps/tools/cli/configuration).
58
+ 2. [Perform token exchange](#perform-token-exchange) to get an access token.
59
+
60
+ #### Perform Token Exchange
61
+ Use [`ShopifyAPI::Auth::TokenExchange`](https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/auth/token_exchange.rb) to
62
+ exchange a [session token](https://shopify.dev/docs/apps/auth/session-tokens) (Shopify Id Token) for an [access token](https://shopify.dev/docs/apps/auth/access-token-types/online.md).
63
+
64
+ #### Input
65
+ | Parameter | Type | Required? | Default Value | Notes |
66
+ | -------------- | ---------------------- | :-------: | :-----------: | ----------------------------------------------------------------------------------------------------------- |
67
+ | `shop` | `String` | Yes | - | A Shopify domain name in the form `{exampleshop}.myshopify.com`. |
68
+ | `session_token` | `String` | Yes| - | The session token (Shopify Id Token) provided by App Bridge in either the request 'Authorization' header or URL param when the app is loaded in Admin. |
69
+ | `requested_token_type` | `TokenExchange::RequestedTokenType` | Yes | - | The type of token requested. Online: `TokenExchange::RequestedTokenType::ONLINE_ACCESS_TOKEN` or offline: `TokenExchange::RequestedTokenType::OFFLINE_ACCESS_TOKEN`. |
70
+
71
+ #### Output
72
+ This method returns the new `ShopifyAPI::Auth::Session` object from the token exchange,
73
+ your app should store this `Session` object to be used later [when making authenticated API calls](#using-oauth-session-to-make-authenticated-api-calls).
74
+
75
+ #### Example
76
+ ```ruby
77
+
78
+ # `shop` is the shop domain name - "this-is-my-example-shop.myshopify.com"
79
+ # `session_token` is the session token provided by App Bridge either in:
80
+ # - the request 'Authorization' header as `Bearer this-is-the-session_token`
81
+ # - or as a URL param `id_token=this-is-the-session_token`
82
+
83
+ def authenticate(shop, session_token)
84
+ session = ShopifyAPI::Auth::TokenExchange.exchange_token(
85
+ shop: shop,
86
+ session_token: session_token,
87
+ requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::OFFLINE_ACCESS_TOKEN,
88
+ # or if you're requesting an online access token:
89
+ # requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::ONLINE_ACCESS_TOKEN,
90
+ )
91
+
92
+ SessionRepository.store_session(session)
93
+ end
94
+
95
+ ```
96
+
97
+ ### Authorization Code Grant Flow
98
+ ##### Steps
26
99
  1. [Add a route to start OAuth](#1-add-a-route-to-start-oauth)
27
100
  2. [Add an Oauth callback route](#2-add-an-oauth-callback-route)
28
101
  3. [Begin OAuth](#3-begin-oauth)
29
102
  4. [Handle OAuth Callback](#4-handle-oauth-callback)
30
- 5. [Using OAuth Session to make authenticated API calls](#5-using-oauth-session-to-make-authenticated-api-calls)
31
103
 
32
- ### 1. Add a route to start OAuth
104
+ #### 1. Add a route to start OAuth
33
105
  Add a route to your app to start the OAuth process.
34
106
 
35
107
  ```ruby
@@ -40,7 +112,7 @@ class ShopifyAuthController < ApplicationController
40
112
  end
41
113
  ```
42
114
 
43
- ### 2. Add an OAuth callback route
115
+ #### 2. Add an OAuth callback route
44
116
  After the app is authenticated with Shopify, the Shopify platform will send a request back to your app using this route
45
117
  (which you will provide as the `redirect_path` parameter to `begin_auth` method, in [step 3 - Begin OAuth](#3-begin-oauth)).
46
118
  ```ruby
@@ -50,7 +122,7 @@ class ShopifyCallbackController < ApplicationController
50
122
  end
51
123
  ```
52
124
 
53
- ### 3. Begin OAuth
125
+ #### 3. Begin OAuth
54
126
  Use [`ShopifyAPI::Auth::Oauth.begin_auth`](https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/auth/oauth.rb#L22) method to start OAuth process for your app.
55
127
 
56
128
  #### Input
@@ -74,7 +146,7 @@ Use [`ShopifyAPI::Auth::Oauth.begin_auth`](https://github.com/Shopify/shopify-ap
74
146
  |`auth_route`|`String`|URI that will be used for redirecting the user to the Shopify Authentication screen|
75
147
  |`cookie`|`ShopifyAPI::Auth::Oauth::SessionCookie`|A session cookie to store on the user's browser. |
76
148
 
77
- #### Example
149
+ ##### Example
78
150
  Your app should take the returned values from the `begin_auth` method and:
79
151
 
80
152
  1. Set the cookie in the user's browser. We strongly recommend that you use secure, httpOnly cookies for this to help prevent session hijacking.
@@ -109,19 +181,19 @@ end
109
181
 
110
182
  ⚠️ You can see a concrete example in the `ShopifyApp` gem's [SessionController](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/sessions_controller.rb).
111
183
 
112
- ### 4. Handle OAuth Callback
184
+ #### 4. Handle OAuth Callback
113
185
  When the user grants permission to the app in Shopify admin, they'll be redirected back to the app's callback route
114
186
  (configured in [Step 2 - Add an OAuth callback route](#2-add-an-oauth-callback-route)).
115
187
 
116
188
  Use [`ShopifyAPI::AuthL::Oauth.validate_auth_callback`](https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/auth/oauth.rb#L60) method to finalize the OAuth process.
117
189
 
118
- #### Input
190
+ ##### Input
119
191
  | Parameter | Type | Notes |
120
192
  | ------------ | --------| ----------------------------------------------------------------------------------------------------------- |
121
193
  | `cookies` | `Hash` | All browser cookies in a hash format with key and value as `String` |
122
194
  | `auth_query` | `ShopifyAPI::Auth::Oauth::AuthQuery`| An `AuthQuery` containing the authorization request information used to validate the request.|
123
195
 
124
- #### Output
196
+ ##### Output
125
197
  This method returns a hash containing the new session and a cookie to be set in the browser in form of:
126
198
  ```ruby
127
199
  {
@@ -134,12 +206,12 @@ This method returns a hash containing the new session and a cookie to be set in
134
206
  |`session`|`ShopifyAPI::Auth::Session`|A session object that contains necessary information to identify the session like `shop`, `access_token`, `scope`, etc.|
135
207
  |`cookie` |`ShopifyAPI::Auth::Oauth::SessionCookie`|A session cookie to store on the user's browser. |
136
208
 
137
- #### Example
209
+ ##### Example
138
210
  Your app should call `validate_auth_callback` to construct the `Session` object and cookie that will be used later for authenticated API requests.
139
211
 
140
212
  1. Call `validate_auth_callback` to construct `Session` and `SessionCookie`.
141
213
  2. Update browser cookies with the new value for the session.
142
- 3. Store the `Session` object to be used later when making authenticated API calls.
214
+ 3. Store the `Session` object to be used later when [making authenticated API calls](#using-oauth-session-to-make-authenticated-api-calls).
143
215
  - See [Make a GraphQL API call](https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/graphql.md), or
144
216
  [Make a REST API call](https://github.com/Shopify/shopify-api-ruby/blob/main/docs/usage/rest.md) for examples on how to use the result `Session` object.
145
217
 
@@ -182,8 +254,8 @@ end
182
254
 
183
255
  ⚠️ 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).
184
256
 
185
- ### 5. Using OAuth Session to make authenticated API calls
186
- Once your OAuth flow is complete, and you have stored your `Session` object from [Step 4 - Handle OAuth Callback](#4-handle-oauth-callback), you may use that `Session` object to make authenticated API calls.
257
+ ## Using OAuth Session to make authenticated API calls
258
+ Once your OAuth flow is complete, and you have persisted your `Session` object, you may use that `Session` object to make authenticated API calls.
187
259
 
188
260
  Example:
189
261
  ```ruby
@@ -21,8 +21,9 @@ module WebhookHandler
21
21
  extend ShopifyAPI::Webhooks::WebhookHandler
22
22
 
23
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"
24
+ def handle(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
+ perform_later(topic: data.topic, shop_domain: data.shop, webhook: data.body)
26
27
  end
27
28
  end
28
29
  end
@@ -5,6 +5,7 @@ module ShopifyAPI
5
5
  module AdminVersions
6
6
  SUPPORTED_ADMIN_VERSIONS = T.let([
7
7
  "unstable",
8
+ "2024-07",
8
9
  "2024-04",
9
10
  "2024-01",
10
11
  "2023-10",
@@ -17,7 +18,7 @@ module ShopifyAPI
17
18
  "2022-01",
18
19
  ], T::Array[String])
19
20
 
20
- LATEST_SUPPORTED_ADMIN_VERSION = T.let("2024-04", String)
21
+ LATEST_SUPPORTED_ADMIN_VERSION = T.let("2024-07", String)
21
22
  end
22
23
 
23
24
  SUPPORTED_ADMIN_VERSIONS = ShopifyAPI::AdminVersions::SUPPORTED_ADMIN_VERSIONS
@@ -28,21 +28,32 @@ module ShopifyAPI
28
28
  variables: T.nilable(T::Hash[T.any(Symbol, String), T.untyped]),
29
29
  headers: T.nilable(T::Hash[T.any(Symbol, String), T.untyped]),
30
30
  tries: Integer,
31
+ response_as_struct: T.nilable(T::Boolean),
32
+ debug: T::Boolean,
31
33
  ).returns(HttpResponse)
32
34
  end
33
- def query(query:, variables: nil, headers: nil, tries: 1)
35
+ def query(
36
+ query:,
37
+ variables: nil,
38
+ headers: nil,
39
+ tries: 1,
40
+ response_as_struct: Context.response_as_struct,
41
+ debug: false
42
+ )
34
43
  body = { query: query, variables: variables }
44
+ search_params = debug ? "?debug=true" : ""
45
+
35
46
  @http_client.request(
36
47
  HttpRequest.new(
37
48
  http_method: :post,
38
- path: "#{@api_version}/graphql.json",
49
+ path: "#{@api_version}/graphql.json#{search_params}",
39
50
  body: body,
40
51
  query: nil,
41
52
  extra_headers: headers,
42
53
  body_type: "application/json",
43
54
  tries: tries,
44
55
  ),
45
- response_as_struct: Context.response_as_struct || false,
56
+ response_as_struct: response_as_struct || false,
46
57
  )
47
58
  end
48
59
  end
@@ -45,11 +45,21 @@ module ShopifyAPI
45
45
  variables: T.nilable(T::Hash[T.any(Symbol, String), T.untyped]),
46
46
  headers: T.nilable(T::Hash[T.any(Symbol, String), T.untyped]),
47
47
  tries: Integer,
48
+ response_as_struct: T.nilable(T::Boolean),
49
+ debug: T::Boolean,
48
50
  ).returns(HttpResponse)
49
51
  end
50
- def query(query:, variables: nil, headers: {}, tries: 1)
52
+ def query(
53
+ query:,
54
+ variables: nil,
55
+ headers: {},
56
+ tries: 1,
57
+ response_as_struct: Context.response_as_struct,
58
+ debug: false
59
+ )
51
60
  T.must(headers).merge!({ @storefront_auth_header => @storefront_access_token })
52
- super(query: query, variables: variables, headers: headers, tries: tries)
61
+ super(query: query, variables: variables, headers: headers, tries: tries,
62
+ response_as_struct: response_as_struct, debug: debug)
53
63
  end
54
64
  end
55
65
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "active_support/inflector"
5
+ require "active_support/core_ext/hash/indifferent_access"
5
6
 
6
7
  module ShopifyAPI
7
8
  module Rest
@@ -16,6 +17,7 @@ module ShopifyAPI
16
17
  @paths = T.let([], T::Array[T::Hash[Symbol, T.any(T::Array[Symbol], String, Symbol)]])
17
18
  @custom_prefix = T.let(nil, T.nilable(String))
18
19
  @read_only_attributes = T.let([], T.nilable(T::Array[Symbol]))
20
+ @atomic_hash_attributes = T.let([], T::Array[Symbol])
19
21
  @aliased_properties = T.let({}, T::Hash[String, String])
20
22
 
21
23
  sig { returns(T::Hash[Symbol, T.untyped]) }
@@ -61,6 +63,9 @@ module ShopifyAPI
61
63
  sig { returns(T::Hash[Symbol, T::Class[T.anything]]) }
62
64
  attr_reader :has_one
63
65
 
66
+ sig { returns(T.nilable(T::Array[Symbol])) }
67
+ attr_reader :atomic_hash_attributes
68
+
64
69
  sig { returns(T.nilable(T::Hash[T.any(Symbol, String), String])) }
65
70
  attr_accessor :headers
66
71
 
@@ -248,7 +253,7 @@ module ShopifyAPI
248
253
  def create_instances_from_response(response:, session:)
249
254
  objects = []
250
255
 
251
- body = T.cast(response.body, T::Hash[String, T.untyped])
256
+ body = T.cast(response.body, T::Hash[String, T.untyped]).with_indifferent_access
252
257
 
253
258
  response_names = json_response_body_names
254
259
 
@@ -417,6 +422,7 @@ module ShopifyAPI
417
422
  ShopifyAPI::Utils::AttributesComparator.compare(
418
423
  stringified_updatable_attributes,
419
424
  stringified_new_attributes,
425
+ atomic_hash_attributes: self.class.atomic_hash_attributes || [],
420
426
  )
421
427
  end
422
428
 
@@ -55,6 +55,7 @@ module ShopifyAPI
55
55
  metafield: Metafield
56
56
  }, T::Hash[Symbol, Class])
57
57
  @has_many = T.let({}, T::Hash[Symbol, Class])
58
+ @atomic_hash_attributes = [:email_marketing_consent]
58
59
  @paths = T.let([
59
60
  {http_method: :delete, operation: :delete, ids: [:id], path: "customers/<id>.json"},
60
61
  {http_method: :get, operation: :count, ids: [], path: "customers/count.json"},
@@ -149,6 +149,15 @@ module ShopifyAPI
149
149
  T.cast(response, T::Array[RecurringApplicationCharge])
150
150
  end
151
151
 
152
+ sig do
153
+ params(session: Auth::Session)
154
+ .returns(T.nilable(RecurringApplicationCharge))
155
+ end
156
+ def current(session: ShopifyAPI::Context.active_session)
157
+ charges = all(session: session)
158
+ charges.select { |charge| charge.status == "active" }.first
159
+ end
160
+
152
161
  end
153
162
 
154
163
  sig do
@@ -66,7 +66,7 @@ module ShopifyAPI
66
66
  @setup_required = T.let(nil, T.nilable(T::Boolean))
67
67
  @shop_owner = T.let(nil, T.nilable(String))
68
68
  @source = T.let(nil, T.nilable(String))
69
- @tax_shipping = T.let(nil, T.nilable(String))
69
+ @tax_shipping = T.let(nil, T.nilable(T::Boolean))
70
70
  @taxes_included = T.let(nil, T.nilable(T::Boolean))
71
71
  @timezone = T.let(nil, T.nilable(String))
72
72
  @transactional_sms_disabled = T.let(nil, T.nilable(T::Boolean))
@@ -179,7 +179,7 @@ module ShopifyAPI
179
179
  attr_reader :shop_owner
180
180
  sig { returns(T.nilable(String)) }
181
181
  attr_reader :source
182
- sig { returns(T.nilable(String)) }
182
+ sig { returns(T.nilable(T::Boolean)) }
183
183
  attr_reader :tax_shipping
184
184
  sig { returns(T.nilable(T::Boolean)) }
185
185
  attr_reader :taxes_included
@@ -216,6 +216,16 @@ module ShopifyAPI
216
216
  T.cast(response, T::Array[Shop])
217
217
  end
218
218
 
219
+ sig do
220
+ params(
221
+ fields: T.untyped,
222
+ session: Auth::Session,
223
+ ).returns(T.nilable(Shop))
224
+ end
225
+ def current(fields: nil, session: ShopifyAPI::Context.active_session)
226
+ all(session: session, fields: fields).first
227
+ end
228
+
219
229
  end
220
230
 
221
231
  end
@@ -55,6 +55,7 @@ module ShopifyAPI
55
55
  metafield: Metafield
56
56
  }, T::Hash[Symbol, Class])
57
57
  @has_many = T.let({}, T::Hash[Symbol, Class])
58
+ @atomic_hash_attributes = [:email_marketing_consent]
58
59
  @paths = T.let([
59
60
  {http_method: :delete, operation: :delete, ids: [:id], path: "customers/<id>.json"},
60
61
  {http_method: :get, operation: :count, ids: [], path: "customers/count.json"},
@@ -149,6 +149,15 @@ module ShopifyAPI
149
149
  T.cast(response, T::Array[RecurringApplicationCharge])
150
150
  end
151
151
 
152
+ sig do
153
+ params(session: Auth::Session)
154
+ .returns(T.nilable(RecurringApplicationCharge))
155
+ end
156
+ def current(session: ShopifyAPI::Context.active_session)
157
+ charges = all(session: session)
158
+ charges.select { |charge| charge.status == "active" }.first
159
+ end
160
+
152
161
  end
153
162
 
154
163
  sig do
@@ -66,7 +66,7 @@ module ShopifyAPI
66
66
  @setup_required = T.let(nil, T.nilable(T::Boolean))
67
67
  @shop_owner = T.let(nil, T.nilable(String))
68
68
  @source = T.let(nil, T.nilable(String))
69
- @tax_shipping = T.let(nil, T.nilable(String))
69
+ @tax_shipping = T.let(nil, T.nilable(T::Boolean))
70
70
  @taxes_included = T.let(nil, T.nilable(T::Boolean))
71
71
  @timezone = T.let(nil, T.nilable(String))
72
72
  @transactional_sms_disabled = T.let(nil, T.nilable(T::Boolean))
@@ -179,7 +179,7 @@ module ShopifyAPI
179
179
  attr_reader :shop_owner
180
180
  sig { returns(T.nilable(String)) }
181
181
  attr_reader :source
182
- sig { returns(T.nilable(String)) }
182
+ sig { returns(T.nilable(T::Boolean)) }
183
183
  attr_reader :tax_shipping
184
184
  sig { returns(T.nilable(T::Boolean)) }
185
185
  attr_reader :taxes_included
@@ -216,6 +216,16 @@ module ShopifyAPI
216
216
  T.cast(response, T::Array[Shop])
217
217
  end
218
218
 
219
+ sig do
220
+ params(
221
+ fields: T.untyped,
222
+ session: Auth::Session,
223
+ ).returns(T.nilable(Shop))
224
+ end
225
+ def current(fields: nil, session: ShopifyAPI::Context.active_session)
226
+ all(session: session, fields: fields).first
227
+ end
228
+
219
229
  end
220
230
 
221
231
  end
@@ -55,6 +55,7 @@ module ShopifyAPI
55
55
  metafield: Metafield
56
56
  }, T::Hash[Symbol, Class])
57
57
  @has_many = T.let({}, T::Hash[Symbol, Class])
58
+ @atomic_hash_attributes = [:email_marketing_consent]
58
59
  @paths = T.let([
59
60
  {http_method: :delete, operation: :delete, ids: [:id], path: "customers/<id>.json"},
60
61
  {http_method: :get, operation: :count, ids: [], path: "customers/count.json"},
@@ -149,6 +149,15 @@ module ShopifyAPI
149
149
  T.cast(response, T::Array[RecurringApplicationCharge])
150
150
  end
151
151
 
152
+ sig do
153
+ params(session: Auth::Session)
154
+ .returns(T.nilable(RecurringApplicationCharge))
155
+ end
156
+ def current(session: ShopifyAPI::Context.active_session)
157
+ charges = all(session: session)
158
+ charges.select { |charge| charge.status == "active" }.first
159
+ end
160
+
152
161
  end
153
162
 
154
163
  sig do