shopify_api 8.1.0 → 9.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +22 -10
- data/docs/graphql.md +191 -0
- data/lib/shopify_api.rb +2 -0
- data/lib/shopify_api/api_version.rb +1 -1
- data/lib/shopify_api/graphql.rb +79 -0
- data/lib/shopify_api/graphql/http_client.rb +22 -0
- data/lib/shopify_api/graphql/railtie.rb +17 -0
- data/lib/shopify_api/graphql/task.rake +100 -0
- data/lib/shopify_api/resources/assigned_fulfillment_order.rb +16 -0
- data/lib/shopify_api/resources/base.rb +8 -0
- data/lib/shopify_api/resources/fulfillment.rb +34 -0
- data/lib/shopify_api/resources/fulfillment_order.rb +137 -0
- data/lib/shopify_api/resources/fulfillment_order_locations_for_move.rb +4 -0
- data/lib/shopify_api/resources/fulfillment_v2.rb +20 -0
- data/lib/shopify_api/resources/order.rb +7 -0
- data/lib/shopify_api/session.rb +3 -3
- data/lib/shopify_api/version.rb +1 -1
- data/test/assigned_fulfillment_order_test.rb +77 -0
- data/test/base_test.rb +14 -0
- data/test/fixtures/assigned_fulfillment_orders.json +78 -0
- data/test/fixtures/fulfillment_order.json +38 -0
- data/test/fixtures/fulfillment_order_locations_for_move.json +18 -0
- data/test/fixtures/fulfillment_orders.json +78 -0
- data/test/fixtures/fulfillments.json +53 -0
- data/test/fixtures/graphql/2019-10.json +1083 -0
- data/test/fixtures/graphql/dummy_schema.rb +16 -0
- data/test/fixtures/graphql/unstable.json +1083 -0
- data/test/fulfillment_order_test.rb +462 -0
- data/test/fulfillment_order_test_helper.rb +7 -0
- data/test/fulfillment_test.rb +164 -1
- data/test/fulfillment_v2_test.rb +62 -0
- data/test/graphql/http_client_test.rb +26 -0
- data/test/graphql_test.rb +147 -0
- data/test/order_test.rb +50 -0
- data/test/session_test.rb +26 -13
- data/test/test_helper.rb +4 -1
- metadata +25 -3
- data/lib/shopify_api/resources/graphql.rb +0 -22
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
namespace :shopify_api do
|
5
|
+
namespace :graphql do
|
6
|
+
desc 'Dumps a local JSON schema file of the Shopify Admin API'
|
7
|
+
task :dump do
|
8
|
+
usage = <<~USAGE
|
9
|
+
|
10
|
+
Usage: rake shopify_api:graphql:dump [<args>]
|
11
|
+
|
12
|
+
Dumps a local JSON schema file of the Shopify Admin API. The schema is specific to an
|
13
|
+
API version and authentication is required (either OAuth or private app).
|
14
|
+
|
15
|
+
Dump the schema file for the 2020-01 API version using private app authentication:
|
16
|
+
$ rake shopify_api:graphql:dump SHOP_URL="https://API_KEY:PASSWORD@SHOP_NAME.myshopify.com" API_VERSION=2020-01
|
17
|
+
|
18
|
+
Dump the schema file for the unstable API version using an OAuth access token:
|
19
|
+
$ rake shopify_api:graphql:dump SHOP_DOMAIN=SHOP_NAME.myshopify.com ACCESS_TOKEN=abc API_VERSION=unstable
|
20
|
+
|
21
|
+
See https://github.com/Shopify/shopify_api#getting-started for more
|
22
|
+
details on getting started with authenticated API calls.
|
23
|
+
|
24
|
+
Arguments:
|
25
|
+
ACCESS_TOKEN OAuth access token (shop specific)
|
26
|
+
API_VERSION API version handle [example: 2020-01]
|
27
|
+
SHOP_DOMAIN Shop domain (without path) [example: SHOP_NAME.myshopify.com]
|
28
|
+
SHOP_URL Shop URL for private apps [example: https://API_KEY:PASSWORD@SHOP_NAME.myshopify.com]
|
29
|
+
USAGE
|
30
|
+
|
31
|
+
access_token = ENV['ACCESS_TOKEN'] || ENV['access_token']
|
32
|
+
api_version = ENV['API_VERSION'] || ENV['api_version']
|
33
|
+
shop_url = ENV['SHOP_URL'] || ENV['shop_url']
|
34
|
+
shop_domain = ENV['SHOP_DOMAIN'] || ENV['shop_domain']
|
35
|
+
|
36
|
+
unless access_token || api_version || shop_url || shop_domain
|
37
|
+
puts usage
|
38
|
+
exit(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
unless shop_url || shop_domain
|
42
|
+
puts 'Error: either SHOP_DOMAIN or SHOP_URL is required for authentication'
|
43
|
+
puts usage
|
44
|
+
exit(1)
|
45
|
+
end
|
46
|
+
|
47
|
+
if shop_url && shop_domain
|
48
|
+
puts 'Error: SHOP_DOMAIN and SHOP_URL cannot be used together. Use one or the other for authentication.'
|
49
|
+
puts usage
|
50
|
+
exit(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
if shop_domain && !access_token
|
54
|
+
puts 'Error: ACCESS_TOKEN required when SHOP_DOMAIN is used'
|
55
|
+
puts usage
|
56
|
+
exit(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
unless api_version
|
60
|
+
puts 'Error: API_VERSION required. Example: 2020-01'
|
61
|
+
puts usage
|
62
|
+
exit(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
Rake::Task['environment'].invoke if Rake::Task.task_defined?('environment')
|
66
|
+
|
67
|
+
ShopifyAPI::ApiVersion.fetch_known_versions
|
68
|
+
ShopifyAPI::ApiVersion.version_lookup_mode = :raise_on_unknown
|
69
|
+
|
70
|
+
shopify_session = ShopifyAPI::Session.new(domain: shop_domain, token: access_token, api_version: api_version)
|
71
|
+
ShopifyAPI::Base.activate_session(shopify_session)
|
72
|
+
|
73
|
+
if shop_url
|
74
|
+
ShopifyAPI::Base.site = shop_url
|
75
|
+
end
|
76
|
+
|
77
|
+
puts "Fetching schema for #{ShopifyAPI::Base.api_version.handle} API version..."
|
78
|
+
|
79
|
+
client = ShopifyAPI::GraphQL::HTTPClient.new(ShopifyAPI::Base.api_version)
|
80
|
+
document = GraphQL.parse('{ __schema { queryType { name } } }')
|
81
|
+
response = client.execute(document: document).to_h
|
82
|
+
|
83
|
+
unless response['data'].present?
|
84
|
+
puts "Error: failed to query the API."
|
85
|
+
puts "Response: #{response}"
|
86
|
+
puts 'Ensure your SHOP_DOMAIN or SHOP_URL are valid and you have valid authentication credentials.'
|
87
|
+
puts usage
|
88
|
+
exit(1)
|
89
|
+
end
|
90
|
+
|
91
|
+
schema_location = ShopifyAPI::GraphQL.schema_location
|
92
|
+
FileUtils.mkdir_p(schema_location) unless Dir.exist?(schema_location)
|
93
|
+
|
94
|
+
schema_file = schema_location.join("#{api_version}.json")
|
95
|
+
GraphQL::Client.dump_schema(client, schema_file.to_s)
|
96
|
+
|
97
|
+
puts "Wrote file #{schema_file}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ShopifyAPI
|
2
|
+
class AssignedFulfillmentOrder < Base
|
3
|
+
|
4
|
+
def initialize(attributes = {}, persisted = false)
|
5
|
+
ShopifyAPI::Base.version_validation!(FulfillmentOrder::MINIMUM_VERSION)
|
6
|
+
super(attributes, persisted)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find(scope, *args)
|
10
|
+
ShopifyAPI::Base.version_validation!(FulfillmentOrder::MINIMUM_VERSION)
|
11
|
+
|
12
|
+
assigned_fulfillment_orders = super(scope, *args)
|
13
|
+
assigned_fulfillment_orders.map { |afo| FulfillmentOrder.new(afo.attributes) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -132,6 +132,14 @@ module ShopifyAPI
|
|
132
132
|
!!early_july_pagination
|
133
133
|
end
|
134
134
|
|
135
|
+
def version_validation!(minimum_version)
|
136
|
+
available_in_version = ShopifyAPI::ApiVersion.find_version(minimum_version)
|
137
|
+
|
138
|
+
unless ShopifyAPI::Base.api_version >= available_in_version
|
139
|
+
raise NotImplementedError, "The minimum supported version is #{minimum_version}."
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
135
143
|
private
|
136
144
|
|
137
145
|
attr_accessor :early_july_pagination
|
@@ -9,5 +9,39 @@ module ShopifyAPI
|
|
9
9
|
def cancel; load_attributes_from_response(post(:cancel, {}, only_id)); end
|
10
10
|
def complete; load_attributes_from_response(post(:complete, {}, only_id)); end
|
11
11
|
def open; load_attributes_from_response(post(:open, {}, only_id)); end
|
12
|
+
|
13
|
+
def order_id=(order_id)
|
14
|
+
prefix_options[:order_id] = order_id
|
15
|
+
end
|
16
|
+
|
17
|
+
def load(attributes, remove_root = false, persisted = false)
|
18
|
+
order_id = attributes['order_id']
|
19
|
+
prefix_options[:order_id] = order_id if order_id
|
20
|
+
super(attributes, remove_root, persisted)
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
if prefix_options[:order_id].present?
|
25
|
+
super
|
26
|
+
else
|
27
|
+
line_items = attributes['line_items_by_fulfillment_order'] || attributes[:line_items_by_fulfillment_order]
|
28
|
+
if line_items.blank?
|
29
|
+
raise ShopifyAPI::ValidationException,
|
30
|
+
"either 'line_items_by_fulfillment_order' or prefix_options[:order_id] is required"
|
31
|
+
end
|
32
|
+
|
33
|
+
fulfillmentV2 = FulfillmentV2.new(attributes)
|
34
|
+
result = fulfillmentV2.save
|
35
|
+
load(fulfillmentV2.attributes, false, true)
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_tracking(tracking_info:, notify_customer:)
|
41
|
+
fulfillmentV2 = FulfillmentV2.new(attributes)
|
42
|
+
result = fulfillmentV2.update_tracking(tracking_info: tracking_info, notify_customer: notify_customer)
|
43
|
+
load(fulfillmentV2.attributes, false, true)
|
44
|
+
result
|
45
|
+
end
|
12
46
|
end
|
13
47
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module ShopifyAPI
|
2
|
+
class FulfillmentOrder < Base
|
3
|
+
MINIMUM_VERSION = '2020-01'
|
4
|
+
|
5
|
+
def initialize(attributes = {}, persisted = false)
|
6
|
+
ShopifyAPI::Base.version_validation!(MINIMUM_VERSION)
|
7
|
+
super(attributes, persisted)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find(scope, *args)
|
11
|
+
ShopifyAPI::Base.version_validation!(MINIMUM_VERSION)
|
12
|
+
|
13
|
+
if scope == :all
|
14
|
+
order_id = args.first&.dig(:params, :order_id)
|
15
|
+
raise ShopifyAPI::ValidationException, "'order_id' is required" if order_id.blank?
|
16
|
+
|
17
|
+
order = ::ShopifyAPI::Order.new(id: order_id)
|
18
|
+
order.fulfillment_orders(args.first[:params].except(:order_id))
|
19
|
+
else
|
20
|
+
super(scope, *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def fulfillments(options = {})
|
25
|
+
fulfillment_hashes = get(:fulfillments, options)
|
26
|
+
fulfillment_hashes.map { |fulfillment_hash| Fulfillment.new(fulfillment_hash) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def locations_for_move
|
30
|
+
locations_for_move_hashes = get(:locations_for_move, {})
|
31
|
+
|
32
|
+
locations_for_move_hashes.map do |locations_for_move_hash|
|
33
|
+
FulfillmentOrderLocationsForMove.new(locations_for_move_hash)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def move(new_location_id:)
|
38
|
+
body = {
|
39
|
+
fulfillment_order: {
|
40
|
+
new_location_id: new_location_id
|
41
|
+
}
|
42
|
+
}
|
43
|
+
keyed_fulfillment_orders = keyed_fulfillment_orders_from_response(post(:move, {}, body.to_json))
|
44
|
+
load_keyed_fulfillment_order(keyed_fulfillment_orders, 'original_fulfillment_order')
|
45
|
+
keyed_fulfillment_orders
|
46
|
+
end
|
47
|
+
|
48
|
+
def cancel
|
49
|
+
keyed_fulfillment_orders = keyed_fulfillment_orders_from_response(post(:cancel, {}, only_id))
|
50
|
+
load_keyed_fulfillment_order(keyed_fulfillment_orders, 'fulfillment_order')
|
51
|
+
keyed_fulfillment_orders
|
52
|
+
end
|
53
|
+
|
54
|
+
def close(message: nil)
|
55
|
+
body = {
|
56
|
+
fulfillment_order: {
|
57
|
+
message: message
|
58
|
+
}
|
59
|
+
}
|
60
|
+
load_attributes_from_response(post(:close, {}, body.to_json))
|
61
|
+
end
|
62
|
+
|
63
|
+
def request_fulfillment(fulfillment_order_line_items: nil, message: nil)
|
64
|
+
body = {
|
65
|
+
fulfillment_request: {
|
66
|
+
fulfillment_order_line_items: fulfillment_order_line_items,
|
67
|
+
message: message
|
68
|
+
}
|
69
|
+
}
|
70
|
+
keyed_fulfillment_orders = keyed_fulfillment_orders_from_response(post(:fulfillment_request, {}, body.to_json))
|
71
|
+
load_keyed_fulfillment_order(keyed_fulfillment_orders, 'original_fulfillment_order')
|
72
|
+
keyed_fulfillment_orders
|
73
|
+
end
|
74
|
+
|
75
|
+
def accept_fulfillment_request(message: nil)
|
76
|
+
body = {
|
77
|
+
fulfillment_request: {
|
78
|
+
message: message
|
79
|
+
}
|
80
|
+
}
|
81
|
+
load_attributes_from_response(post('fulfillment_request/accept', {}, body.to_json))
|
82
|
+
end
|
83
|
+
|
84
|
+
def reject_fulfillment_request(message: nil)
|
85
|
+
body = {
|
86
|
+
fulfillment_request: {
|
87
|
+
message: message
|
88
|
+
}
|
89
|
+
}
|
90
|
+
load_attributes_from_response(post('fulfillment_request/reject', {}, body.to_json))
|
91
|
+
end
|
92
|
+
|
93
|
+
def request_cancellation(message: nil)
|
94
|
+
body = {
|
95
|
+
cancellation_request: {
|
96
|
+
message: message
|
97
|
+
}
|
98
|
+
}
|
99
|
+
load_attributes_from_response(post(:cancellation_request, {}, body.to_json))
|
100
|
+
end
|
101
|
+
|
102
|
+
def accept_cancellation_request(message: nil)
|
103
|
+
body = {
|
104
|
+
cancellation_request: {
|
105
|
+
message: message
|
106
|
+
}
|
107
|
+
}
|
108
|
+
load_attributes_from_response(post('cancellation_request/accept', {}, body.to_json))
|
109
|
+
end
|
110
|
+
|
111
|
+
def reject_cancellation_request(message: nil)
|
112
|
+
body = {
|
113
|
+
cancellation_request: {
|
114
|
+
message: message
|
115
|
+
}
|
116
|
+
}
|
117
|
+
load_attributes_from_response(post('cancellation_request/reject', {}, body.to_json))
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def load_keyed_fulfillment_order(keyed_fulfillment_orders, key)
|
123
|
+
if keyed_fulfillment_orders[key]&.attributes
|
124
|
+
load(keyed_fulfillment_orders[key].attributes, false, true)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def keyed_fulfillment_orders_from_response(response)
|
129
|
+
return load_attributes_from_response(response) if response.code != '200'
|
130
|
+
|
131
|
+
keyed_fulfillment_orders = ActiveSupport::JSON.decode(response.body)
|
132
|
+
keyed_fulfillment_orders.transform_values do |fulfillment_order_attributes|
|
133
|
+
FulfillmentOrder.new(fulfillment_order_attributes) if fulfillment_order_attributes
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ShopifyAPI
|
2
|
+
class FulfillmentV2 < Base
|
3
|
+
self.element_name = 'fulfillment'
|
4
|
+
|
5
|
+
def initialize(attributes = {}, persisted = false)
|
6
|
+
ShopifyAPI::Base.version_validation!(FulfillmentOrder::MINIMUM_VERSION)
|
7
|
+
super(attributes, persisted)
|
8
|
+
end
|
9
|
+
|
10
|
+
def update_tracking(tracking_info:, notify_customer:)
|
11
|
+
body = {
|
12
|
+
fulfillment: {
|
13
|
+
tracking_info: tracking_info,
|
14
|
+
notify_customer: notify_customer
|
15
|
+
}
|
16
|
+
}
|
17
|
+
load_attributes_from_response(post(:update_tracking, {}, body.to_json))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -30,6 +30,13 @@ module ShopifyAPI
|
|
30
30
|
Transaction.create(capture_transaction)
|
31
31
|
end
|
32
32
|
|
33
|
+
def fulfillment_orders(options = {})
|
34
|
+
ShopifyAPI::Base.version_validation!(FulfillmentOrder::MINIMUM_VERSION)
|
35
|
+
|
36
|
+
fulfillment_order_hashes = get(:fulfillment_orders, options)
|
37
|
+
fulfillment_order_hashes.map { |fulfillment_order_hash| FulfillmentOrder.new(fulfillment_order_hash) }
|
38
|
+
end
|
39
|
+
|
33
40
|
class ClientDetails < Base
|
34
41
|
end
|
35
42
|
end
|
data/lib/shopify_api/session.rb
CHANGED
@@ -91,9 +91,9 @@ module ShopifyAPI
|
|
91
91
|
self.extra = extra
|
92
92
|
end
|
93
93
|
|
94
|
-
def create_permission_url(scope, redirect_uri =
|
95
|
-
params = {:
|
96
|
-
params[:
|
94
|
+
def create_permission_url(scope, redirect_uri, options = {})
|
95
|
+
params = { client_id: api_key, scope: scope.join(','), redirect_uri: redirect_uri }
|
96
|
+
params[:state] = options[:state] if options[:state]
|
97
97
|
construct_oauth_url("authorize", params)
|
98
98
|
end
|
99
99
|
|
data/lib/shopify_api/version.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fulfillment_order_test_helper'
|
3
|
+
|
4
|
+
class AssignedFulFillmentOrderTest < Test::Unit::TestCase
|
5
|
+
include FulfillmentOrderTestHelper
|
6
|
+
|
7
|
+
def setup
|
8
|
+
super
|
9
|
+
@url_prefix = url_prefix_for_activated_session_for('2020-01')
|
10
|
+
@fulfillment_order_fixture = load_fixture('assigned_fulfillment_orders')
|
11
|
+
end
|
12
|
+
|
13
|
+
context "AssignedFulfillmentOrder" do
|
14
|
+
context ".new" do
|
15
|
+
should "raise NotImplementedError when api_version is older than 2020-01" do
|
16
|
+
url_prefix_for_activated_session_for('2019-10')
|
17
|
+
|
18
|
+
assert_raises NotImplementedError do
|
19
|
+
ShopifyAPI::AssignedFulfillmentOrder.new(ActiveSupport::JSON.decode(@fulfillment_order_fixture))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#all" do
|
25
|
+
should "raise NotImplementedError when api_version is older than 2020-01" do
|
26
|
+
@url_prefix = url_prefix_for_activated_session_for('2019-10')
|
27
|
+
|
28
|
+
fake 'assigned_fulfillment_orders',
|
29
|
+
url: "#{@url_prefix}/assigned_fulfillment_orders.json",
|
30
|
+
method: :get,
|
31
|
+
body: @fulfillment_order_fixture,
|
32
|
+
extension: false
|
33
|
+
|
34
|
+
assert_raises NotImplementedError do
|
35
|
+
ShopifyAPI::AssignedFulfillmentOrder.all(params: { assigned_status: 'cancellation_requested' })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
should "list assigned fulfillment orders by assigned_status" do
|
40
|
+
fake 'assigned_fulfillment_orders',
|
41
|
+
url: "#{@url_prefix}/assigned_fulfillment_orders.json?assigned_status=cancellation_requested",
|
42
|
+
method: :get,
|
43
|
+
body: @fulfillment_order_fixture,
|
44
|
+
extension: false
|
45
|
+
|
46
|
+
assigned_fulfillment_orders = ShopifyAPI::AssignedFulfillmentOrder.all(
|
47
|
+
params: { assigned_status: 'cancellation_requested' }
|
48
|
+
)
|
49
|
+
|
50
|
+
assert_equal 2, assigned_fulfillment_orders.count
|
51
|
+
assigned_fulfillment_orders.each do |fulfillment_order|
|
52
|
+
assert fulfillment_order.is_a?(ShopifyAPI::FulfillmentOrder)
|
53
|
+
assert_equal 'in_progress', fulfillment_order.status
|
54
|
+
assert_equal 'cancellation_accepted', fulfillment_order.request_status
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
should "be able to list assigned fulfillment orders by location_ids" do
|
59
|
+
assigned_location_id = 905684977
|
60
|
+
fake 'assigned_fulfillment_orders',
|
61
|
+
url: "#{@url_prefix}/assigned_fulfillment_orders.json?location_ids%5B%5D=#{assigned_location_id}",
|
62
|
+
method: :get,
|
63
|
+
body: @fulfillment_order_fixture, extension: false
|
64
|
+
|
65
|
+
assigned_fulfillment_orders = ShopifyAPI::AssignedFulfillmentOrder.all(
|
66
|
+
params: { location_ids: [assigned_location_id] }
|
67
|
+
)
|
68
|
+
|
69
|
+
assert_equal 2, assigned_fulfillment_orders.count
|
70
|
+
assigned_fulfillment_orders.each do |fulfillment_order|
|
71
|
+
assert fulfillment_order.is_a?(ShopifyAPI::FulfillmentOrder)
|
72
|
+
assert_equal assigned_location_id, fulfillment_order.assigned_location_id
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|