shopify_api 4.9.0 → 5.2.4

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 (94) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/probots.yml +2 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +8 -0
  6. data/.travis.yml +0 -4
  7. data/CHANGELOG +63 -0
  8. data/README.md +36 -14
  9. data/lib/active_resource/detailed_log_subscriber.rb +25 -2
  10. data/lib/shopify_api/connection.rb +5 -3
  11. data/lib/shopify_api/limits.rb +1 -2
  12. data/lib/shopify_api/resources/abandoned_checkout.rb +7 -0
  13. data/lib/shopify_api/resources/access_scope.rb +5 -0
  14. data/lib/shopify_api/resources/api_permission.rb +9 -0
  15. data/lib/shopify_api/resources/asset.rb +8 -8
  16. data/lib/shopify_api/resources/billing_address.rb +1 -1
  17. data/lib/shopify_api/resources/checkout.rb +27 -1
  18. data/lib/shopify_api/resources/collection_publication.rb +10 -0
  19. data/lib/shopify_api/resources/currency.rb +6 -0
  20. data/lib/shopify_api/resources/custom_collection.rb +3 -3
  21. data/lib/shopify_api/resources/{customer_invite_message.rb → customer_invite.rb} +0 -0
  22. data/lib/shopify_api/resources/graphql.rb +22 -0
  23. data/lib/shopify_api/resources/image.rb +2 -2
  24. data/lib/shopify_api/resources/inventory_item.rb +6 -0
  25. data/lib/shopify_api/resources/inventory_level.rb +55 -0
  26. data/lib/shopify_api/resources/line_item.rb +9 -1
  27. data/lib/shopify_api/resources/location.rb +4 -0
  28. data/lib/shopify_api/resources/o_auth.rb +8 -0
  29. data/lib/shopify_api/resources/order.rb +16 -4
  30. data/lib/shopify_api/resources/payment.rb +7 -0
  31. data/lib/shopify_api/resources/ping.rb +3 -0
  32. data/lib/shopify_api/resources/ping/conversation.rb +42 -0
  33. data/lib/shopify_api/resources/ping/delivery_confirmation_details.rb +10 -0
  34. data/lib/shopify_api/resources/ping/message.rb +8 -0
  35. data/lib/shopify_api/resources/product.rb +4 -4
  36. data/lib/shopify_api/resources/product_publication.rb +10 -0
  37. data/lib/shopify_api/resources/publication.rb +5 -0
  38. data/lib/shopify_api/resources/shipping_line.rb +1 -1
  39. data/lib/shopify_api/resources/shipping_rate.rb +7 -0
  40. data/lib/shopify_api/resources/shop.rb +4 -4
  41. data/lib/shopify_api/resources/smart_collection.rb +6 -2
  42. data/lib/shopify_api/resources/tender_transaction.rb +6 -0
  43. data/lib/shopify_api/session.rb +1 -1
  44. data/lib/shopify_api/version.rb +1 -1
  45. data/service.yml +8 -0
  46. data/shopify_api.gemspec +4 -1
  47. data/test/abandoned_checkouts_test.rb +29 -0
  48. data/test/api_permission_test.rb +9 -0
  49. data/test/checkouts_test.rb +72 -4
  50. data/test/collection_publication_test.rb +40 -0
  51. data/test/currency_test.rb +21 -0
  52. data/test/detailed_log_subscriber_test.rb +19 -1
  53. data/test/fixtures/abandoned_checkout.json +184 -0
  54. data/test/fixtures/abandoned_checkouts.json +186 -0
  55. data/test/fixtures/checkout.json +160 -0
  56. data/test/fixtures/checkouts.json +25 -49
  57. data/test/fixtures/collection_publication.json +11 -0
  58. data/test/fixtures/collection_publications.json +13 -0
  59. data/test/fixtures/currencies.json +25 -0
  60. data/test/fixtures/inventory_level.json +7 -0
  61. data/test/fixtures/inventory_levels.json +24 -0
  62. data/test/fixtures/order_with_properties.json +373 -0
  63. data/test/fixtures/payment.json +7 -0
  64. data/test/fixtures/payments.json +9 -0
  65. data/test/fixtures/ping/conversation.json +1 -0
  66. data/test/fixtures/ping/failed_delivery_confirmation.json +1 -0
  67. data/test/fixtures/ping/message.json +1 -0
  68. data/test/fixtures/ping/successful_delivery_confirmation.json +1 -0
  69. data/test/fixtures/product_publication.json +11 -0
  70. data/test/fixtures/product_publications.json +13 -0
  71. data/test/fixtures/publications.json +9 -0
  72. data/test/fixtures/shipping_rates.json +12 -0
  73. data/test/fixtures/smart_collection_products.json +155 -0
  74. data/test/fixtures/tender_transactions.json +52 -0
  75. data/test/inventory_level_test.rb +59 -0
  76. data/test/location_test.rb +14 -0
  77. data/test/order_test.rb +29 -1
  78. data/test/payment_test.rb +19 -0
  79. data/test/ping/conversation_test.rb +71 -0
  80. data/test/ping/message_test.rb +23 -0
  81. data/test/product_publication_test.rb +40 -0
  82. data/test/publication_test.rb +12 -0
  83. data/test/session_test.rb +11 -11
  84. data/test/shipping_rate_test.rb +17 -0
  85. data/test/smart_collection_test.rb +25 -0
  86. data/test/tender_transaction_test.rb +18 -0
  87. data/test/test_helper.rb +8 -5
  88. data/test/variant_test.rb +4 -1
  89. metadata +102 -10
  90. data/lib/shopify_api/resources/discount.rb +0 -11
  91. data/test/discount_test.rb +0 -52
  92. data/test/fixtures/discount.json +0 -17
  93. data/test/fixtures/discount_disabled.json +0 -17
  94. data/test/fixtures/discounts.json +0 -34
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require 'graphql/client'
3
+ require 'graphql/client/http'
4
+
5
+ module ShopifyAPI
6
+ # GraphQL API.
7
+ class GraphQL
8
+ def initialize
9
+ uri = Base.site.dup
10
+ uri.path = '/admin/api/graphql.json'
11
+ @http = ::GraphQL::Client::HTTP.new(uri.to_s) do
12
+ define_method(:headers) do |_context|
13
+ Base.headers
14
+ end
15
+ end
16
+ @schema = ::GraphQL::Client.load_schema(@http)
17
+ @client = ::GraphQL::Client.new(schema: @schema, execute: @http)
18
+ end
19
+
20
+ delegate :parse, :query, to: :@client
21
+ end
22
+ end
@@ -1,13 +1,13 @@
1
1
  module ShopifyAPI
2
2
  class Image < Base
3
3
  init_prefix :product
4
-
4
+
5
5
  # generate a method for each possible image variant
6
6
  [:pico, :icon, :thumb, :small, :compact, :medium, :large, :grande, :original].each do |m|
7
7
  reg_exp_match = "/\\1_#{m}.\\2"
8
8
  define_method(m) { src.gsub(/\/(.*)\.(\w{2,4})/, reg_exp_match) }
9
9
  end
10
-
10
+
11
11
  def attach_image(data, filename = nil)
12
12
  attributes['attachment'] = Base64.encode64(data)
13
13
  attributes['filename'] = filename unless filename.nil?
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class InventoryItem < Base
5
+ end
6
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class InventoryLevel < Base
5
+
6
+ # The default path structure in ActiveResource for delete would result in:
7
+ # /admin/inventory_levels/#{ inventory_level.id }.json?#{ params }, but since
8
+ # InventroyLevels are a second class resource made up of a Where and a What
9
+ # (Location and InventoryItem), it does not have a resource ID. Here we
10
+ # redefine element_path to remove the id so HTTP DELETE requests go to
11
+ # /admin/inventory_levels.json?#{ params } instead.
12
+ #
13
+ def self.element_path(prefix_options = {}, query_options = nil)
14
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
15
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
16
+ end
17
+
18
+ def destroy
19
+ load_attributes_from_response(
20
+ self.class.delete('/', location_id: location_id, inventory_item_id: inventory_item_id)
21
+ )
22
+ end
23
+
24
+ def connect(relocate_if_necessary: nil)
25
+ body = { location_id: location_id, inventory_item_id: inventory_item_id }
26
+ body[:relocate_if_necessary] = relocate_if_necessary unless relocate_if_necessary.nil?
27
+ load_attributes_from_response(
28
+ self.class.post(:connect, {}, body.to_json)
29
+ )
30
+ end
31
+
32
+ def set(new_available, disconnect_if_necessary: nil)
33
+ body = {
34
+ location_id: location_id,
35
+ inventory_item_id: inventory_item_id,
36
+ available: new_available
37
+ }
38
+ body[:disconnect_if_necessary] = disconnect_if_necessary unless disconnect_if_necessary.nil?
39
+ load_attributes_from_response(
40
+ self.class.post(:set, {}, body.to_json)
41
+ )
42
+ end
43
+
44
+ def adjust(available_adjustment)
45
+ body = {
46
+ location_id: location_id,
47
+ inventory_item_id: inventory_item_id,
48
+ available_adjustment: available_adjustment
49
+ }
50
+ load_attributes_from_response(
51
+ self.class.post(:adjust, {}, body.to_json)
52
+ )
53
+ end
54
+ end
55
+ end
@@ -1,6 +1,14 @@
1
1
  module ShopifyAPI
2
- class LineItem < Base
2
+ class LineItem < Base
3
3
  class Property < Base
4
+ def initialize(*args)
5
+ attributes = args[0] || {}
6
+ persisted = args[1] || false
7
+ super
8
+ rescue NameError
9
+ attributes = attributes.to_hash
10
+ self
11
+ end
4
12
  end
5
13
  end
6
14
  end
@@ -1,4 +1,8 @@
1
1
  module ShopifyAPI
2
2
  class Location < Base
3
+
4
+ def inventory_levels
5
+ ShopifyAPI::InventoryLevel.find(:all, from: "/admin/locations/#{id}/inventory_levels.json")
6
+ end
3
7
  end
4
8
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This resource is deprecated and will be removed in a future version of this gem.
4
+ # Use ShopifyAPI::ApiPermission.destroy instead
5
+
1
6
  module ShopifyAPI
2
7
  class OAuth < Base
3
8
  self.collection_name = 'oauth'
4
9
 
5
10
  def self.revoke
11
+ warn '[DEPRECATED] ShopifyAPI::OAuth#revoke is deprecated and will be removed in a future version. ' \
12
+ 'Use ShopifyAPI::ApiPermission#destroy instead.'
13
+
6
14
  delete(:revoke)
7
15
  end
8
16
  end
@@ -3,8 +3,13 @@ module ShopifyAPI
3
3
  include Events
4
4
  include Metafields
5
5
 
6
- def close; load_attributes_from_response(post(:close, {}, only_id)); end
7
- def open; load_attributes_from_response(post(:open, {}, only_id)); end
6
+ def close
7
+ load_attributes_from_response(post(:close, {}, only_id))
8
+ end
9
+
10
+ def open
11
+ load_attributes_from_response(post(:open, {}, only_id))
12
+ end
8
13
 
9
14
  def cancel(options = {})
10
15
  load_attributes_from_response(post(:cancel, {}, options.to_json))
@@ -14,8 +19,15 @@ module ShopifyAPI
14
19
  Transaction.find(:all, :params => { :order_id => id })
15
20
  end
16
21
 
17
- def capture(amount = "")
18
- Transaction.create(:amount => amount, :kind => "capture", :order_id => id)
22
+ def capture(amount = "", currency: nil)
23
+ capture_transaction = {
24
+ amount: amount,
25
+ kind: "capture",
26
+ order_id: id,
27
+ }
28
+ capture_transaction[:currency] = currency if currency
29
+
30
+ Transaction.create(capture_transaction)
19
31
  end
20
32
 
21
33
  class ClientDetails < Base
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class Payment < Base
5
+ self.prefix = '/admin/checkouts/:checkout_id/'
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{File.dirname(__FILE__)}/ping/*").each { |file| require file }
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ module Ping
5
+ class Conversation < Base
6
+ self.prefix = "/admin/api/ping-api/v1/"
7
+
8
+ def send_message(message_attrs)
9
+ message = ShopifyAPI::Ping::Message.new(
10
+ message_attrs.merge(conversation_id: id)
11
+ )
12
+
13
+ message.save
14
+ message
15
+ end
16
+
17
+ def successful_delivery(message_id:, delivery_timestamp:)
18
+ delivery_details = ShopifyAPI::Ping::DeliveryConfirmationDetails.new(
19
+ delivery_attrs(message_id, delivery_timestamp).merge(delivered: true)
20
+ )
21
+ delivery_details.save
22
+ delivery_details
23
+ end
24
+
25
+ def failed_delivery(message_id:, delivery_timestamp:, details:)
26
+ delivery_details = ShopifyAPI::Ping::DeliveryConfirmationDetails.new(
27
+ delivery_attrs(message_id, delivery_timestamp).merge(delivered: false, details: details)
28
+ )
29
+ delivery_details.save
30
+ delivery_details
31
+ end
32
+
33
+ def delivery_attrs(message_id, delivery_timestamp)
34
+ {
35
+ conversation_id: id,
36
+ message_id: message_id,
37
+ confirmation_timestamp: delivery_timestamp,
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ module Ping
5
+ class DeliveryConfirmationDetails < Base
6
+ self.prefix = "/admin/api/ping-api/v1/conversations/:conversation_id/messages/:message_id/"
7
+ self.collection_name = "delivery_confirmation"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ module Ping
4
+ class Message < Base
5
+ self.prefix = "/admin/api/ping-api/v1/conversations/:conversation_id/"
6
+ end
7
+ end
8
+ end
@@ -13,19 +13,19 @@ module ShopifyAPI
13
13
  format % prices.min
14
14
  end
15
15
  end
16
-
16
+
17
17
  def collections
18
18
  CustomCollection.find(:all, :params => {:product_id => self.id})
19
19
  end
20
-
20
+
21
21
  def smart_collections
22
22
  SmartCollection.find(:all, :params => {:product_id => self.id})
23
23
  end
24
-
24
+
25
25
  def add_to_collection(collection)
26
26
  collection.add_product(self)
27
27
  end
28
-
28
+
29
29
  def remove_from_collection(collection)
30
30
  collection.remove_product(self)
31
31
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class ProductPublication < Base
4
+ init_prefix :publication
5
+
6
+ def publication_id
7
+ @prefix_options[:publication_id]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class Publication < Base
4
+ end
5
+ end
@@ -1,4 +1,4 @@
1
1
  module ShopifyAPI
2
2
  class ShippingLine < Base
3
- end
3
+ end
4
4
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class ShippingRate < Base
5
+ self.prefix = '/admin/checkouts/:checkout_id/'
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  module ShopifyAPI
2
- # Shop object. Use Shop.current to receive
2
+ # Shop object. Use Shop.current to receive
3
3
  # the shop.
4
4
  class Shop < Base
5
5
  def self.current(options={})
@@ -11,13 +11,13 @@ module ShopifyAPI
11
11
  end
12
12
 
13
13
  def add_metafield(metafield)
14
- raise ArgumentError, "You can only add metafields to resource that has been saved" if new?
14
+ raise ArgumentError, "You can only add metafields to resource that has been saved" if new?
15
15
  metafield.save
16
16
  metafield
17
17
  end
18
-
18
+
19
19
  def events
20
20
  Event.find(:all)
21
21
  end
22
- end
22
+ end
23
23
  end
@@ -3,8 +3,12 @@ module ShopifyAPI
3
3
  include Events
4
4
  include Metafields
5
5
 
6
- def products
7
- Product.find(:all, :params => {:collection_id => self.id})
6
+ def products(options = {})
7
+ if options.present?
8
+ Product.find(:all, from: "/admin/smart_collections/#{id}/products.json", params: options)
9
+ else
10
+ Product.find(:all, params: { collection_id: id })
11
+ end
8
12
  end
9
13
 
10
14
  def order(options={})
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class TenderTransaction < Base
5
+ end
6
+ end
@@ -51,7 +51,7 @@ module ShopifyAPI
51
51
  end
52
52
 
53
53
  def validate_signature(params)
54
- params = params.with_indifferent_access
54
+ params = (params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params).with_indifferent_access
55
55
  return false unless signature = params[:hmac]
56
56
 
57
57
  calculated_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new(), secret, encoded_params_for_signature(params))
@@ -1,3 +1,3 @@
1
1
  module ShopifyAPI
2
- VERSION = "4.9.0"
2
+ VERSION = "5.2.4"
3
3
  end
data/service.yml ADDED
@@ -0,0 +1,8 @@
1
+ audience: partner
2
+ classification: library
3
+ org_line: App & Partner Platform
4
+ owners:
5
+ - Shopify/app-partner-dev-tools-education
6
+ slack_channels:
7
+ - dev-tools-education
8
+ - api-patterns-team
data/shopify_api.gemspec CHANGED
@@ -23,14 +23,17 @@ Gem::Specification.new do |s|
23
23
  s.summary = %q{ShopifyAPI is a lightweight gem for accessing the Shopify admin REST web services}
24
24
  s.license = "MIT"
25
25
 
26
- s.required_ruby_version = ">= 2.0"
26
+ s.required_ruby_version = ">= 2.1"
27
27
 
28
28
  s.add_runtime_dependency("activeresource", ">= 3.0.0")
29
29
  s.add_runtime_dependency("rack")
30
+ s.add_runtime_dependency("graphql-client")
30
31
 
31
32
  s.add_development_dependency("mocha", ">= 0.9.8")
32
33
  s.add_development_dependency("fakeweb")
33
34
  s.add_development_dependency("minitest", ">= 4.0")
34
35
  s.add_development_dependency("rake")
35
36
  s.add_development_dependency("timecop")
37
+ s.add_development_dependency("pry")
38
+ s.add_development_dependency("pry-byebug")
36
39
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ class AbandonedCheckoutsTest < Test::Unit::TestCase
5
+ def setup
6
+ super
7
+
8
+ @expected_checkouts = JSON.parse(load_fixture('abandoned_checkouts'))['checkouts']
9
+ @expected_checkout_id = JSON.parse(load_fixture('abandoned_checkout'))['checkout']['id']
10
+ end
11
+
12
+ test ":create creates a checkout" do
13
+ fake 'checkouts', method: :post, status: 201, body: load_fixture('abandoned_checkout')
14
+
15
+ checkout = ShopifyAPI::AbandonedCheckout.create
16
+
17
+ assert_equal @expected_checkout_id, checkout.id
18
+ assert_equal true, checkout.attributes.include?(:abandoned_checkout_url)
19
+ end
20
+
21
+ test "get all checkouts indexed by token" do
22
+ fake 'checkouts', method: :get, status: 200, body: load_fixture('abandoned_checkouts')
23
+
24
+ checkouts = ShopifyAPI::AbandonedCheckout.all
25
+
26
+ assert_equal @expected_checkout_id, checkouts.first.id
27
+ assert_equal @expected_checkouts.size, checkouts.size
28
+ end
29
+ end