Skip to content

Commit 069fd51

Browse files
authored
Merge branch 'main' into remove-passing-public-storefront-token
2 parents 2fb80f2 + 37b95e4 commit 069fd51

File tree

146 files changed

+32452
-113
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+32452
-113
lines changed

BREAKING_CHANGES_FOR_V15.md

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,65 @@ client = ShopifyAPI::Clients::Graphql::Storefront.new(
3434
```
3535

3636
For more information on private vs public Storefront access tokens, see [Shopify's authentication documentation](https://shopify.dev/docs/api/usage/authentication#getting-started-with-private-access).
37+
## Removal of `LATEST_SUPPORTED_ADMIN_VERSION` and `RELEASE_CANDIDATE_ADMIN_VERSION` constants
38+
39+
The `LATEST_SUPPORTED_ADMIN_VERSION` and `RELEASE_CANDIDATE_ADMIN_VERSION` constants have been removed to prevent semantic versioning (semver) breaking changes. Previously, these constants would automatically update every quarter when new API versions were released, causing unintended breaking changes for apps.
40+
41+
### Migration Guide
42+
43+
**If you were using these constants directly:**
44+
45+
```ruby
46+
# Before (v14 and earlier)
47+
api_version = ShopifyAPI::LATEST_SUPPORTED_ADMIN_VERSION
48+
# or
49+
api_version = ShopifyAPI::RELEASE_CANDIDATE_ADMIN_VERSION
50+
51+
# After (v15+)
52+
api_version = "2025-07" # Explicitly specify the version you want to use
53+
```
54+
55+
**In your Context.setup:**
56+
57+
The `api_version` parameter has always been required in `Context.setup`, so most apps should not be affected. However, you must now explicitly specify which API version you want to use:
58+
59+
```ruby
60+
# Before (v14 and earlier)
61+
ShopifyAPI::Context.setup(
62+
api_key: "<api-key>",
63+
api_secret_key: "<api-secret-key>",
64+
host: "<https://application-host-name.com>",
65+
scope: "read_orders,read_products,etc",
66+
is_embedded: true,
67+
api_version: ShopifyAPI::LATEST_SUPPORTED_ADMIN_VERSION, # This constant no longer exists
68+
is_private: false,
69+
)
70+
71+
# After (v15+)
72+
ShopifyAPI::Context.setup(
73+
api_key: "<api-key>",
74+
api_secret_key: "<api-secret-key>",
75+
host: "<https://application-host-name.com>",
76+
scope: "read_orders,read_products,etc",
77+
is_embedded: true,
78+
api_version: "2025-07", # Explicitly specify the version
79+
is_private: false,
80+
)
81+
```
82+
83+
**Finding the right API version:**
84+
85+
You can see all supported API versions by referencing:
86+
```ruby
87+
ShopifyAPI::SUPPORTED_ADMIN_VERSIONS
88+
# => ["unstable", "2025-10", "2025-07", "2025-04", ...]
89+
```
90+
91+
**Why this change?**
92+
By requiring explicit version specification, apps can:
93+
- Control when they upgrade to new API versions
94+
- Test thoroughly before upgrading
95+
- Avoid unexpected breaking changes from automatic version updates
3796

3897
## Removal of `ShopifyAPI::Webhooks::Handler`
3998

@@ -57,8 +116,8 @@ module WebhookHandler
57116
extend ShopifyAPI::Webhooks::WebhookHandler
58117

59118
class << self
60-
def handle_webhook(data:)
61-
puts "Received webhook! topic: #{data.topic} shop: #{data.shop} body: #{data.body} webhook_id: #{data.webhook_id} api_version: #{data.api_version"
119+
def handle(data:)
120+
puts "Received webhook! topic: #{data.topic} shop: #{data.shop} body: #{data.body} webhook_id: #{data.webhook_id} api_version: #{data.api_version}"
62121
end
63122
end
64123
end

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
Note: For changes to the API, see https://shopify.dev/changelog?filter=api
44
## Unreleased
55

6+
### 15.0.0
7+
8+
- ⚠️ [Breaking] Removed deprecated `ShopifyAPI::Webhooks::Handler` interface. Apps must migrate to `ShopifyAPI::Webhooks::WebhookHandler` which provides `webhook_id` and `api_version` in addition to `topic`, `shop`, and `body`. See [BREAKING_CHANGES_FOR_V15.md](BREAKING_CHANGES_FOR_V15.md) for migration guide.
9+
- Add support for 2025-10 API version
10+
- Removed deprecated REST resources: `AccessScope`, `Product`, `ProductImage`, `PriceRule`, `ProductListing`, `ProductResourceFeedback`
11+
- Updated `LATEST_SUPPORTED_ADMIN_VERSION` to `2025-10`
12+
- [#1411](https://github.com/Shopify/shopify-api-ruby/pull/1411) Remove `LATEST_SUPPORTED_ADMIN_VERSION` and `RELEASE_CANDIDATE_ADMIN_VERSION` constants to prevent semver violations. Developers must now explicitly specify API versions. See the [migration guide](BREAKING_CHANGES_FOR_V15.md#removal-of-latest_supported_admin_version-and-release_candidate_admin_version-constants) for details.
13+
614
- [#1405](https://github.com/Shopify/shopify-api-ruby/pull/1405) Fix webhook registration for topics containing dots (e.g., `customer.tags_added`, `customer.tags_removed`)
715

816
## 14.11.1

docs/usage/webhooks.md

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,9 @@ module WebhookHandler
2929
end
3030
```
3131

32-
**Note:** As of version 13.5.0 the `ShopifyAPI::Webhooks::Handler` class is still available to be used but will be removed in a future version of the gem.
33-
3432
### Best Practices
3533
It is recommended that in order to respond quickly to the Shopify webhook request that the handler not do any heavy logic or network calls, rather it should simply enqueue the work in some job queue in order to be executed later.
3634

37-
### Webhook Handler for versions 13.4.0 and prior
38-
If you want to register for an http webhook you need to implement a webhook handler which the `shopify_api` gem can use to determine how to process your webhook. You can make multiple implementations (one per topic) or you can make one implementation capable of handling all the topics you want to subscribe to. To do this simply make a module or class that includes or extends `ShopifyAPI::Webhooks::Handler` and implement the handle method which accepts the following named parameters: topic: `String`, shop: `String`, and body: `Hash[String, untyped]`. An example implementation is shown below:
39-
40-
```ruby
41-
module WebhookHandler
42-
extend ShopifyAPI::Webhooks::Handler
43-
44-
class << self
45-
def handle(topic:, shop:, body:)
46-
puts "Received webhook! topic: #{topic} shop: #{shop} body: #{body}"
47-
end
48-
end
49-
end
50-
```
51-
5235
## Add to Webhook Registry
5336

5437
The next step is to add all the webhooks you would like to subscribe to for any shop to the webhook registry. To do this you can call `ShopifyAPI::Webhooks::Registry.add_registration` for each webhook you would like to handle. `add_registration` accepts a topic string, a delivery_method symbol (currently supporting `:http`, `:event_bridge`, and `:pub_sub`), a webhook path (the relative path for an http webhook) and a handler. This only needs to be done once when the app is started and we recommend doing this at the same time that you setup `ShopifyAPI::Context`. An example is shown below to register an http webhook:

lib/shopify_api/admin_versions.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module ShopifyAPI
55
module AdminVersions
66
SUPPORTED_ADMIN_VERSIONS = T.let([
77
"unstable",
8+
"2026-01",
89
"2025-10",
910
"2025-07",
1011
"2025-04",
@@ -22,12 +23,7 @@ module AdminVersions
2223
"2022-04",
2324
"2022-01",
2425
], T::Array[String])
25-
26-
LATEST_SUPPORTED_ADMIN_VERSION = T.let("2025-07", String)
27-
RELEASE_CANDIDATE_ADMIN_VERSION = T.let("2025-10", String)
2826
end
2927

3028
SUPPORTED_ADMIN_VERSIONS = ShopifyAPI::AdminVersions::SUPPORTED_ADMIN_VERSIONS
31-
LATEST_SUPPORTED_ADMIN_VERSION = ShopifyAPI::AdminVersions::LATEST_SUPPORTED_ADMIN_VERSION
32-
RELEASE_CANDIDATE_ADMIN_VERSION = ShopifyAPI::AdminVersions::RELEASE_CANDIDATE_ADMIN_VERSION
3329
end

lib/shopify_api/context.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Context
77

88
@api_key = T.let("", String)
99
@api_secret_key = T.let("", String)
10-
@api_version = T.let(LATEST_SUPPORTED_ADMIN_VERSION, String)
10+
@api_version = T.let("", String)
1111
@api_host = T.let(nil, T.nilable(String))
1212
@scope = T.let(Auth::AuthScopes.new, Auth::AuthScopes)
1313
@is_private = T.let(false, T::Boolean)
@@ -101,8 +101,8 @@ def load_rest_resources(api_version:)
101101
@rest_resource_loader&.setup
102102
@rest_resource_loader&.unload
103103

104-
# No resources for the unstable version or the release candidate version
105-
return if api_version == "unstable" || api_version == RELEASE_CANDIDATE_ADMIN_VERSION
104+
# No resources for the unstable version
105+
return if api_version == "unstable"
106106

107107
version_folder_name = api_version.gsub("-", "_")
108108

@@ -155,7 +155,7 @@ def embedded?
155155

156156
sig { returns(T::Boolean) }
157157
def setup?
158-
[api_key, api_secret_key, T.must(host)].none?(&:empty?)
158+
[api_key, api_secret_key, api_version, T.must(host)].none?(&:empty?)
159159
end
160160

161161
sig { returns(T.nilable(Auth::Session)) }
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)