shopify_api 8.1.0 → 9.0.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +22 -10
  4. data/docs/graphql.md +191 -0
  5. data/lib/shopify_api.rb +2 -0
  6. data/lib/shopify_api/api_version.rb +1 -1
  7. data/lib/shopify_api/graphql.rb +79 -0
  8. data/lib/shopify_api/graphql/http_client.rb +22 -0
  9. data/lib/shopify_api/graphql/railtie.rb +17 -0
  10. data/lib/shopify_api/graphql/task.rake +100 -0
  11. data/lib/shopify_api/resources/assigned_fulfillment_order.rb +16 -0
  12. data/lib/shopify_api/resources/base.rb +8 -0
  13. data/lib/shopify_api/resources/fulfillment.rb +34 -0
  14. data/lib/shopify_api/resources/fulfillment_order.rb +137 -0
  15. data/lib/shopify_api/resources/fulfillment_order_locations_for_move.rb +4 -0
  16. data/lib/shopify_api/resources/fulfillment_v2.rb +20 -0
  17. data/lib/shopify_api/resources/order.rb +7 -0
  18. data/lib/shopify_api/session.rb +3 -3
  19. data/lib/shopify_api/version.rb +1 -1
  20. data/test/assigned_fulfillment_order_test.rb +77 -0
  21. data/test/base_test.rb +14 -0
  22. data/test/fixtures/assigned_fulfillment_orders.json +78 -0
  23. data/test/fixtures/fulfillment_order.json +38 -0
  24. data/test/fixtures/fulfillment_order_locations_for_move.json +18 -0
  25. data/test/fixtures/fulfillment_orders.json +78 -0
  26. data/test/fixtures/fulfillments.json +53 -0
  27. data/test/fixtures/graphql/2019-10.json +1083 -0
  28. data/test/fixtures/graphql/dummy_schema.rb +16 -0
  29. data/test/fixtures/graphql/unstable.json +1083 -0
  30. data/test/fulfillment_order_test.rb +462 -0
  31. data/test/fulfillment_order_test_helper.rb +7 -0
  32. data/test/fulfillment_test.rb +164 -1
  33. data/test/fulfillment_v2_test.rb +62 -0
  34. data/test/graphql/http_client_test.rb +26 -0
  35. data/test/graphql_test.rb +147 -0
  36. data/test/order_test.rb +50 -0
  37. data/test/session_test.rb +26 -13
  38. data/test/test_helper.rb +4 -1
  39. metadata +25 -3
  40. 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,4 @@
1
+ module ShopifyAPI
2
+ class FulfillmentOrderLocationsForMove < Base
3
+ end
4
+ 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
@@ -91,9 +91,9 @@ module ShopifyAPI
91
91
  self.extra = extra
92
92
  end
93
93
 
94
- def create_permission_url(scope, redirect_uri = nil)
95
- params = {:client_id => api_key, :scope => scope.join(',')}
96
- params[:redirect_uri] = redirect_uri if redirect_uri
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
 
@@ -1,3 +1,3 @@
1
1
  module ShopifyAPI
2
- VERSION = "8.1.0"
2
+ VERSION = "9.0.0"
3
3
  end
@@ -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