shopify_api 4.9.0 → 9.4.1

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 (281) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE.md +36 -0
  4. data/.github/probots.yml +2 -0
  5. data/.github/workflows/build.yml +41 -0
  6. data/.gitignore +5 -1
  7. data/.rubocop.yml +28 -0
  8. data/.rubocop_todo.yml +75 -0
  9. data/CHANGELOG.md +491 -0
  10. data/CONTRIBUTING.md +1 -1
  11. data/Gemfile +6 -2
  12. data/Gemfile.lock +151 -0
  13. data/Gemfile_ar41 +5 -0
  14. data/Gemfile_ar50 +5 -0
  15. data/Gemfile_ar51 +5 -0
  16. data/Gemfile_ar_master +0 -1
  17. data/README.md +492 -100
  18. data/RELEASING +10 -9
  19. data/Rakefile +21 -5
  20. data/SECURITY.md +59 -0
  21. data/docker-compose.yml +13 -0
  22. data/docs/_config.yml +1 -0
  23. data/docs/_includes/footer.html +28 -0
  24. data/docs/_includes/head.html +28 -0
  25. data/docs/_layouts/index.html +57 -0
  26. data/docs/graphql.md +241 -0
  27. data/docs/index.md +639 -0
  28. data/lib/active_resource/connection_ext.rb +1 -0
  29. data/lib/active_resource/detailed_log_subscriber.rb +43 -7
  30. data/lib/active_resource/json_errors.rb +8 -2
  31. data/lib/shopify_api.rb +16 -6
  32. data/lib/shopify_api/api_access.rb +57 -0
  33. data/lib/shopify_api/api_version.rb +206 -0
  34. data/lib/shopify_api/connection.rb +7 -4
  35. data/lib/shopify_api/countable.rb +3 -2
  36. data/lib/shopify_api/disable_prefix_check.rb +31 -0
  37. data/lib/shopify_api/events.rb +2 -1
  38. data/lib/shopify_api/graphql.rb +103 -0
  39. data/lib/shopify_api/graphql/http_client.rb +22 -0
  40. data/lib/shopify_api/graphql/railtie.rb +17 -0
  41. data/lib/shopify_api/graphql/task.rake +100 -0
  42. data/lib/shopify_api/limits.rb +9 -9
  43. data/lib/shopify_api/message_enricher.rb +25 -0
  44. data/lib/shopify_api/meta.rb +14 -0
  45. data/lib/shopify_api/metafields.rb +5 -4
  46. data/lib/shopify_api/paginated_collection.rb +69 -0
  47. data/lib/shopify_api/pagination_link_headers.rb +34 -0
  48. data/lib/shopify_api/resources.rb +3 -1
  49. data/lib/shopify_api/resources/abandoned_checkout.rb +7 -0
  50. data/lib/shopify_api/resources/access_scope.rb +10 -0
  51. data/lib/shopify_api/resources/access_token.rb +1 -0
  52. data/lib/shopify_api/resources/address.rb +1 -0
  53. data/lib/shopify_api/resources/announcement.rb +2 -1
  54. data/lib/shopify_api/resources/api_permission.rb +9 -0
  55. data/lib/shopify_api/resources/application_charge.rb +1 -0
  56. data/lib/shopify_api/resources/application_credit.rb +1 -0
  57. data/lib/shopify_api/resources/array_base.rb +13 -0
  58. data/lib/shopify_api/resources/article.rb +3 -2
  59. data/lib/shopify_api/resources/asset.rb +28 -23
  60. data/lib/shopify_api/resources/assigned_fulfillment_order.rb +16 -0
  61. data/lib/shopify_api/resources/base.rb +101 -24
  62. data/lib/shopify_api/resources/billing_address.rb +2 -1
  63. data/lib/shopify_api/resources/blog.rb +2 -1
  64. data/lib/shopify_api/resources/carrier_service.rb +1 -0
  65. data/lib/shopify_api/resources/cart.rb +2 -1
  66. data/lib/shopify_api/resources/checkout.rb +27 -1
  67. data/lib/shopify_api/resources/collect.rb +2 -0
  68. data/lib/shopify_api/resources/collection.rb +14 -0
  69. data/lib/shopify_api/resources/collection_listing.rb +11 -1
  70. data/lib/shopify_api/resources/collection_publication.rb +10 -0
  71. data/lib/shopify_api/resources/comment.rb +20 -5
  72. data/lib/shopify_api/resources/country.rb +1 -0
  73. data/lib/shopify_api/resources/currency.rb +6 -0
  74. data/lib/shopify_api/resources/custom_collection.rb +7 -6
  75. data/lib/shopify_api/resources/customer.rb +2 -1
  76. data/lib/shopify_api/resources/customer_group.rb +1 -0
  77. data/lib/shopify_api/resources/{customer_invite_message.rb → customer_invite.rb} +1 -0
  78. data/lib/shopify_api/resources/customer_saved_search.rb +4 -1
  79. data/lib/shopify_api/resources/discount_code.rb +1 -0
  80. data/lib/shopify_api/resources/discount_code_batch.rb +34 -0
  81. data/lib/shopify_api/resources/draft_order.rb +1 -0
  82. data/lib/shopify_api/resources/draft_order_invoice.rb +1 -0
  83. data/lib/shopify_api/resources/event.rb +2 -0
  84. data/lib/shopify_api/resources/fulfillment.rb +46 -3
  85. data/lib/shopify_api/resources/fulfillment_event.rb +2 -1
  86. data/lib/shopify_api/resources/fulfillment_order.rb +151 -0
  87. data/lib/shopify_api/resources/fulfillment_order_locations_for_move.rb +5 -0
  88. data/lib/shopify_api/resources/fulfillment_request.rb +1 -0
  89. data/lib/shopify_api/resources/fulfillment_service.rb +1 -0
  90. data/lib/shopify_api/resources/fulfillment_v2.rb +21 -0
  91. data/lib/shopify_api/resources/gift_card.rb +1 -0
  92. data/lib/shopify_api/resources/image.rb +4 -3
  93. data/lib/shopify_api/resources/inventory_item.rb +6 -0
  94. data/lib/shopify_api/resources/inventory_level.rb +54 -0
  95. data/lib/shopify_api/resources/line_item.rb +10 -1
  96. data/lib/shopify_api/resources/location.rb +4 -0
  97. data/lib/shopify_api/resources/marketing_event.rb +3 -0
  98. data/lib/shopify_api/resources/metafield.rb +2 -0
  99. data/lib/shopify_api/resources/note_attribute.rb +1 -0
  100. data/lib/shopify_api/resources/option.rb +1 -0
  101. data/lib/shopify_api/resources/order.rb +25 -5
  102. data/lib/shopify_api/resources/order_risk.rb +1 -0
  103. data/lib/shopify_api/resources/page.rb +1 -0
  104. data/lib/shopify_api/resources/payment.rb +7 -0
  105. data/lib/shopify_api/resources/payment_details.rb +1 -0
  106. data/lib/shopify_api/resources/ping.rb +3 -0
  107. data/lib/shopify_api/resources/policy.rb +1 -0
  108. data/lib/shopify_api/resources/price_rule.rb +1 -1
  109. data/lib/shopify_api/resources/product.rb +33 -7
  110. data/lib/shopify_api/resources/product_listing.rb +9 -1
  111. data/lib/shopify_api/resources/product_publication.rb +10 -0
  112. data/lib/shopify_api/resources/province.rb +1 -0
  113. data/lib/shopify_api/resources/publication.rb +5 -0
  114. data/lib/shopify_api/resources/receipt.rb +1 -0
  115. data/lib/shopify_api/resources/recurring_application_charge.rb +4 -1
  116. data/lib/shopify_api/resources/redirect.rb +1 -0
  117. data/lib/shopify_api/resources/refund.rb +6 -4
  118. data/lib/shopify_api/resources/report.rb +1 -0
  119. data/lib/shopify_api/resources/resource_feedback.rb +1 -1
  120. data/lib/shopify_api/resources/rule.rb +1 -0
  121. data/lib/shopify_api/resources/script_tag.rb +1 -0
  122. data/lib/shopify_api/resources/shipping_address.rb +1 -0
  123. data/lib/shopify_api/resources/shipping_line.rb +2 -1
  124. data/lib/shopify_api/resources/shipping_rate.rb +7 -0
  125. data/lib/shopify_api/resources/shipping_zone.rb +1 -0
  126. data/lib/shopify_api/resources/shop.rb +10 -7
  127. data/lib/shopify_api/resources/smart_collection.rb +3 -3
  128. data/lib/shopify_api/resources/storefront_access_token.rb +1 -0
  129. data/lib/shopify_api/resources/tax_line.rb +1 -0
  130. data/lib/shopify_api/resources/tax_service.rb +1 -0
  131. data/lib/shopify_api/resources/tender_transaction.rb +6 -0
  132. data/lib/shopify_api/resources/theme.rb +1 -0
  133. data/lib/shopify_api/resources/transaction.rb +1 -0
  134. data/lib/shopify_api/resources/usage_charge.rb +1 -0
  135. data/lib/shopify_api/resources/user.rb +1 -0
  136. data/lib/shopify_api/resources/variant.rb +35 -0
  137. data/lib/shopify_api/resources/webhook.rb +1 -0
  138. data/lib/shopify_api/session.rb +109 -45
  139. data/lib/shopify_api/version.rb +2 -1
  140. data/lib/verify_docs.rb +8 -0
  141. data/service.yml +8 -0
  142. data/shopify_api.gemspec +19 -8
  143. data/test/abandoned_checkouts_test.rb +29 -0
  144. data/test/access_scope_test.rb +23 -0
  145. data/test/access_token_test.rb +6 -5
  146. data/test/active_resource/json_errors_test.rb +6 -6
  147. data/test/api_access_test.rb +153 -0
  148. data/test/api_permission_test.rb +9 -0
  149. data/test/api_version_test.rb +157 -0
  150. data/test/application_charge_test.rb +30 -27
  151. data/test/application_credit_test.rb +10 -9
  152. data/test/article_test.rb +34 -35
  153. data/test/asset_test.rb +14 -6
  154. data/test/assigned_fulfillment_order_test.rb +78 -0
  155. data/test/base_test.rb +147 -59
  156. data/test/blog_test.rb +4 -3
  157. data/test/carrier_service_test.rb +7 -6
  158. data/test/cart_test.rb +2 -1
  159. data/test/checkouts_test.rb +72 -4
  160. data/test/collect_test.rb +4 -3
  161. data/test/collection_listing_test.rb +56 -13
  162. data/test/collection_publication_test.rb +40 -0
  163. data/test/collection_test.rb +50 -0
  164. data/test/countable_test.rb +3 -2
  165. data/test/currency_test.rb +21 -0
  166. data/test/custom_collection_test.rb +4 -3
  167. data/test/customer_saved_search_test.rb +18 -8
  168. data/test/customer_test.rb +22 -14
  169. data/test/detailed_log_subscriber_test.rb +113 -19
  170. data/test/discount_code_batch_test.rb +41 -0
  171. data/test/discount_code_test.rb +22 -16
  172. data/test/draft_order_test.rb +68 -52
  173. data/test/fixtures/abandoned_checkout.json +184 -0
  174. data/test/fixtures/abandoned_checkouts.json +186 -0
  175. data/test/fixtures/access_scopes.json +10 -0
  176. data/test/fixtures/api_versions.json +38 -0
  177. data/test/fixtures/apis.json +42 -0
  178. data/test/fixtures/assigned_fulfillment_orders.json +80 -0
  179. data/test/fixtures/checkout.json +160 -0
  180. data/test/fixtures/checkouts.json +25 -49
  181. data/test/fixtures/collection.json +17 -0
  182. data/test/fixtures/collection_listing_product_ids2.json +1 -0
  183. data/test/fixtures/collection_products.json +47 -0
  184. data/test/fixtures/collection_publication.json +11 -0
  185. data/test/fixtures/collection_publications.json +13 -0
  186. data/test/fixtures/currencies.json +25 -0
  187. data/test/fixtures/discount_code_batch.json +14 -0
  188. data/test/fixtures/discount_code_batch_discount_codes.json +21 -0
  189. data/test/fixtures/fulfillment_order.json +39 -0
  190. data/test/fixtures/fulfillment_order_locations_for_move.json +18 -0
  191. data/test/fixtures/fulfillment_orders.json +80 -0
  192. data/test/fixtures/fulfillments.json +53 -0
  193. data/test/fixtures/graphql/2019-10.json +1083 -0
  194. data/test/fixtures/graphql/dummy_schema.rb +16 -0
  195. data/test/fixtures/graphql/unstable.json +1083 -0
  196. data/test/fixtures/inventory_level.json +7 -0
  197. data/test/fixtures/inventory_levels.json +24 -0
  198. data/test/fixtures/order_with_properties.json +373 -0
  199. data/test/fixtures/payment.json +7 -0
  200. data/test/fixtures/payments.json +9 -0
  201. data/test/fixtures/ping/conversation.json +1 -0
  202. data/test/fixtures/ping/failed_delivery_confirmation.json +1 -0
  203. data/test/fixtures/ping/message.json +1 -0
  204. data/test/fixtures/ping/successful_delivery_confirmation.json +1 -0
  205. data/test/fixtures/product_listing_product_ids.json +1 -1
  206. data/test/fixtures/product_listing_product_ids2.json +1 -0
  207. data/test/fixtures/product_publication.json +11 -0
  208. data/test/fixtures/product_publications.json +13 -0
  209. data/test/fixtures/publications.json +9 -0
  210. data/test/fixtures/shipping_rates.json +12 -0
  211. data/test/fixtures/smart_collection_products.json +155 -0
  212. data/test/fixtures/tender_transactions.json +52 -0
  213. data/test/fulfillment_event_test.rb +31 -26
  214. data/test/fulfillment_order_test.rb +530 -0
  215. data/test/fulfillment_order_test_helper.rb +8 -0
  216. data/test/fulfillment_request_test.rb +10 -8
  217. data/test/fulfillment_service_test.rb +13 -12
  218. data/test/fulfillment_test.rb +198 -20
  219. data/test/fulfillment_v2_test.rb +66 -0
  220. data/test/gift_card_test.rb +6 -4
  221. data/test/graphql/http_client_test.rb +26 -0
  222. data/test/graphql_test.rb +190 -0
  223. data/test/image_test.rb +19 -17
  224. data/test/inventory_level_test.rb +68 -0
  225. data/test/lib/webmock_extensions/last_request.rb +16 -0
  226. data/test/limits_test.rb +4 -3
  227. data/test/location_test.rb +15 -0
  228. data/test/marketing_event_test.rb +21 -21
  229. data/test/message_enricher_test.rb +45 -0
  230. data/test/meta_test.rb +47 -0
  231. data/test/metafield_test.rb +30 -20
  232. data/test/order_risk_test.rb +17 -16
  233. data/test/order_test.rb +110 -17
  234. data/test/pagination_test.rb +290 -0
  235. data/test/payment_test.rb +19 -0
  236. data/test/policy_test.rb +6 -5
  237. data/test/price_rule_test.rb +20 -15
  238. data/test/product_listing_test.rb +72 -15
  239. data/test/product_publication_test.rb +40 -0
  240. data/test/product_test.rb +80 -19
  241. data/test/publication_test.rb +12 -0
  242. data/test/recurring_application_charge_test.rb +105 -50
  243. data/test/redirect_test.rb +4 -3
  244. data/test/refund_test.rb +22 -17
  245. data/test/report_test.rb +12 -10
  246. data/test/resource_feedback_test.rb +14 -13
  247. data/test/script_tag_test.rb +10 -9
  248. data/test/session_test.rb +497 -111
  249. data/test/shipping_rate_test.rb +17 -0
  250. data/test/shipping_zone_test.rb +4 -3
  251. data/test/shop_test.rb +47 -33
  252. data/test/smart_collection_test.rb +5 -4
  253. data/test/storefront_access_token_test.rb +13 -15
  254. data/test/tax_service_test.rb +7 -3
  255. data/test/tender_transaction_test.rb +18 -0
  256. data/test/test_helper.rb +98 -67
  257. data/test/transaction_test.rb +4 -3
  258. data/test/usage_charge_test.rb +12 -8
  259. data/test/user_test.rb +4 -3
  260. data/test/variant_test.rb +50 -20
  261. data/test/webhook_test.rb +10 -7
  262. metadata +196 -37
  263. data/.travis.yml +0 -36
  264. data/CHANGELOG +0 -292
  265. data/Gemfile_ar30 +0 -6
  266. data/Gemfile_ar31 +0 -6
  267. data/Gemfile_ar32 +0 -6
  268. data/Gemfile_ar40 +0 -6
  269. data/bin/shopify +0 -3
  270. data/lib/active_resource/base_ext.rb +0 -21
  271. data/lib/active_resource/disable_prefix_check.rb +0 -36
  272. data/lib/active_resource/to_query.rb +0 -10
  273. data/lib/shopify_api/json_format.rb +0 -18
  274. data/lib/shopify_api/resources/discount.rb +0 -11
  275. data/lib/shopify_api/resources/o_auth.rb +0 -9
  276. data/test/discount_test.rb +0 -52
  277. data/test/fixtures/discount.json +0 -17
  278. data/test/fixtures/discount_disabled.json +0 -17
  279. data/test/fixtures/discounts.json +0 -34
  280. data/test/fixtures/o_auth_revoke.json +0 -5
  281. data/test/o_auth_test.rb +0 -8
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Report < Base
3
4
  end
@@ -13,7 +13,7 @@ module ShopifyAPI
13
13
 
14
14
  def save
15
15
  return super unless persisted?
16
- raise ExistingFeedbackSaved.new(EXISTING_FEEDBACK_SAVED_ERROR_MESSAGE)
16
+ raise ExistingFeedbackSaved, EXISTING_FEEDBACK_SAVED_ERROR_MESSAGE
17
17
  end
18
18
  end
19
19
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Rule < Base
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class ScriptTag < Base
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class ShippingAddress < Base
3
4
  end
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class ShippingLine < Base
3
- end
4
+ end
4
5
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class ShippingRate < Base
5
+ self.resource_prefix = "checkouts/:checkout_id/"
6
+ end
7
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class ShippingZone < Base
3
4
  end
@@ -1,23 +1,26 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
- # Shop object. Use Shop.current to receive
3
+ # Shop object. Use Shop.current to receive
3
4
  # the shop.
4
5
  class Shop < Base
5
- def self.current(options={})
6
- find(:one, options.merge({from: "/admin/shop.#{format.extension}"}))
6
+ include ActiveResource::Singleton
7
+
8
+ def self.current(options = {})
9
+ find(options)
7
10
  end
8
11
 
9
12
  def metafields(**options)
10
- Metafield.find :all, params: options
13
+ Metafield.find(:all, params: options)
11
14
  end
12
15
 
13
16
  def add_metafield(metafield)
14
- raise ArgumentError, "You can only add metafields to resource that has been saved" if new?
17
+ raise ArgumentError, "You can only add metafields to resource that has been saved" if new?
15
18
  metafield.save
16
19
  metafield
17
20
  end
18
-
21
+
19
22
  def events
20
23
  Event.find(:all)
21
24
  end
22
- end
25
+ end
23
26
  end
@@ -1,15 +1,15 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class SmartCollection < Base
3
4
  include Events
4
5
  include Metafields
5
6
 
6
7
  def products
7
- Product.find(:all, :params => {:collection_id => self.id})
8
+ Product.find(:all, params: { collection_id: id })
8
9
  end
9
10
 
10
- def order(options={})
11
+ def order(options = {})
11
12
  load_attributes_from_response(put(:order, options, only_id))
12
13
  end
13
-
14
14
  end
15
15
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class StorefrontAccessToken < Base
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class TaxLine < Base
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class TaxService < Base
3
4
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class TenderTransaction < Base
5
+ end
6
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Theme < Base
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Transaction < Base
3
4
  init_prefix :order
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class UsageCharge < Base
3
4
  init_prefix :recurring_application_charge
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class User < Base
3
4
  end
@@ -1,8 +1,43 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Variant < Base
3
4
  include Metafields
4
5
  include DisablePrefixCheck
5
6
 
6
7
  conditional_prefix :product
8
+
9
+ def initialize(*)
10
+ super
11
+ attributes.except!('old_inventory_quantity')
12
+ end
13
+
14
+ def inventory_quantity_adjustment=(new_value)
15
+ raise_deprecated_inventory_call('inventory_quantity_adjustment')
16
+ super
17
+ end
18
+
19
+ def inventory_quantity=(new_value)
20
+ raise_deprecated_inventory_call('inventory_quantity')
21
+ super
22
+ end
23
+
24
+ def old_inventory_quantity=(new_value)
25
+ raise_deprecated_inventory_call('old_inventory_quantity')
26
+ super
27
+ end
28
+
29
+ def save
30
+ attributes.except!('inventory_quantity')
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ def raise_deprecated_inventory_call(parameter)
37
+ raise(
38
+ ShopifyAPI::ValidationException,
39
+ "'#{parameter}' is deprecated - see https://help.shopify.com/en/api/guides/inventory-migration-guide",
40
+ )
41
+ end
7
42
  end
8
43
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Webhook < Base
3
4
  end
@@ -1,60 +1,78 @@
1
+ # frozen_string_literal: true
1
2
  require 'openssl'
2
3
  require 'rack'
3
4
 
4
5
  module ShopifyAPI
5
-
6
6
  class ValidationException < StandardError
7
7
  end
8
8
 
9
9
  class Session
10
- cattr_accessor :api_key, :secret, :protocol, :myshopify_domain, :port
11
- self.protocol = 'https'
10
+ SECONDS_IN_A_DAY = 24 * 60 * 60
11
+
12
+ cattr_accessor :api_key, :secret, :myshopify_domain
12
13
  self.myshopify_domain = 'myshopify.com'
13
14
 
14
- attr_accessor :url, :token, :name, :extra
15
+ attr_accessor :domain, :token, :name, :extra
16
+ attr_reader :api_version, :access_scopes
17
+ alias_method :url, :domain
15
18
 
16
19
  class << self
17
-
18
20
  def setup(params)
19
- params.each { |k,value| public_send("#{k}=", value) }
21
+ params.each { |k, value| public_send("#{k}=", value) }
22
+ end
23
+
24
+ def temp(domain:, token:, api_version: ShopifyAPI::Base.api_version, &block)
25
+ session = new(domain: domain, token: token, api_version: api_version)
26
+
27
+ with_session(session, &block)
20
28
  end
21
29
 
22
- def temp(domain, token, &block)
23
- session = new(domain, token)
24
- original_site = ShopifyAPI::Base.site.to_s
25
- original_token = ShopifyAPI::Base.headers['X-Shopify-Access-Token']
26
- original_session = new(original_site, original_token)
30
+ def with_session(session, &_block)
31
+ original_session = extract_current_session
32
+ original_user = ShopifyAPI::Base.user
33
+ original_password = ShopifyAPI::Base.password
27
34
 
28
35
  begin
36
+ ShopifyAPI::Base.clear_session
29
37
  ShopifyAPI::Base.activate_session(session)
30
38
  yield
31
39
  ensure
32
40
  ShopifyAPI::Base.activate_session(original_session)
41
+ ShopifyAPI::Base.user = original_user
42
+ ShopifyAPI::Base.password = original_password
33
43
  end
34
44
  end
35
45
 
36
- def prepare_url(url)
37
- return nil if url.blank?
46
+ def with_version(api_version, &block)
47
+ original_session = extract_current_session
48
+ session = new(domain: original_session.site, token: original_session.token, api_version: api_version)
49
+
50
+ with_session(session, &block)
51
+ end
52
+
53
+ def prepare_domain(domain)
54
+ return nil if domain.blank?
38
55
  # remove http:// or https://
39
- url = url.strip.gsub(/\Ahttps?:\/\//, '')
56
+ domain = domain.strip.gsub(%r{\Ahttps?://}, '')
40
57
  # extract host, removing any username, password or path
41
- shop = URI.parse("https://#{url}").host
58
+ shop = URI.parse("https://#{domain}").host
42
59
  # extract subdomain of .myshopify.com
43
- if idx = shop.index(".")
60
+ if (idx = shop.index("."))
44
61
  shop = shop.slice(0, idx)
45
62
  end
46
63
  return nil if shop.empty?
47
- shop = "#{shop}.#{myshopify_domain}"
48
- port ? "#{shop}:#{port}" : shop
64
+ "#{shop}.#{myshopify_domain}"
49
65
  rescue URI::InvalidURIError
50
66
  nil
51
67
  end
52
68
 
53
69
  def validate_signature(params)
54
- params = params.with_indifferent_access
55
- return false unless signature = params[:hmac]
70
+ params = (params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params).with_indifferent_access
71
+ return false unless (signature = params[:hmac])
56
72
 
57
- calculated_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new(), secret, encoded_params_for_signature(params))
73
+ calculated_signature = OpenSSL::HMAC.hexdigest(
74
+ OpenSSL::Digest.new('SHA256'), secret, encoded_params_for_signature(params)
75
+ )
58
76
 
59
77
  Rack::Utils.secure_compare(calculated_signature, signature)
60
78
  end
@@ -63,40 +81,51 @@ module ShopifyAPI
63
81
 
64
82
  def encoded_params_for_signature(params)
65
83
  params = params.except(:signature, :hmac, :action, :controller)
66
- params.map{|k,v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}"}.sort.join('&')
84
+ params.map { |k, v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}" }.sort.join('&')
85
+ end
86
+
87
+ def extract_current_session
88
+ site = ShopifyAPI::Base.site.to_s
89
+ token = ShopifyAPI::Base.headers['X-Shopify-Access-Token']
90
+ version = ShopifyAPI::Base.api_version
91
+ new(domain: site, token: token, api_version: version)
67
92
  end
68
93
  end
69
94
 
70
- def initialize(url, token = nil, extra = {})
71
- self.url = self.class.prepare_url(url)
95
+ def initialize(domain:, token:, access_scopes: nil, api_version: ShopifyAPI::Base.api_version, extra: {})
96
+ self.domain = self.class.prepare_domain(domain)
97
+ self.api_version = api_version
72
98
  self.token = token
99
+ self.access_scopes = access_scopes
73
100
  self.extra = extra
74
101
  end
75
102
 
76
- def create_permission_url(scope, redirect_uri = nil)
77
- params = {:client_id => api_key, :scope => scope.join(',')}
78
- params[:redirect_uri] = redirect_uri if redirect_uri
79
- "#{site}/oauth/authorize?#{parameterize(params)}"
103
+ def create_permission_url(scope, redirect_uri, options = {})
104
+ params = { client_id: api_key, scope: ShopifyAPI::ApiAccess.new(scope).to_s, redirect_uri: redirect_uri }
105
+ params[:state] = options[:state] if options[:state]
106
+ params["grant_options[]".to_sym] = options[:grant_options] if options[:grant_options]
107
+ construct_oauth_url("authorize", params)
80
108
  end
81
109
 
82
110
  def request_token(params)
83
111
  return token if token
84
112
 
85
- unless self.class.validate_signature(params) && params[:timestamp].to_i > 24.hours.ago.utc.to_i
113
+ twenty_four_hours_ago = Time.now.utc.to_i - SECONDS_IN_A_DAY
114
+ unless self.class.validate_signature(params) && params[:timestamp].to_i > twenty_four_hours_ago
86
115
  raise ShopifyAPI::ValidationException, "Invalid Signature: Possible malicious login"
87
116
  end
88
117
 
89
- response = access_token_request(params['code'])
118
+ response = access_token_request(params[:code])
90
119
  if response.code == "200"
91
120
  self.extra = JSON.parse(response.body)
92
121
  self.token = extra.delete('access_token')
93
122
 
94
- if expires_in = extra.delete('expires_in')
123
+ if (expires_in = extra.delete('expires_in'))
95
124
  extra['expires_at'] = Time.now.utc.to_i + expires_in
96
125
  end
97
126
  token
98
127
  else
99
- raise RuntimeError, response.msg
128
+ raise response.msg
100
129
  end
101
130
  end
102
131
 
@@ -105,11 +134,19 @@ module ShopifyAPI
105
134
  end
106
135
 
107
136
  def site
108
- "#{protocol}://#{url}/admin"
137
+ "https://#{domain}"
138
+ end
139
+
140
+ def api_version=(version)
141
+ @api_version = if ApiVersion::NullVersion.matches?(version)
142
+ ApiVersion::NullVersion
143
+ else
144
+ ApiVersion.find_version(version)
145
+ end
109
146
  end
110
147
 
111
148
  def valid?
112
- url.present? && token.present?
149
+ domain.present? && token.present? && api_version.is_a?(ApiVersion)
113
150
  end
114
151
 
115
152
  def expires_in
@@ -127,18 +164,45 @@ module ShopifyAPI
127
164
  expires_in <= 0
128
165
  end
129
166
 
167
+ def hash
168
+ state.hash
169
+ end
170
+
171
+ def ==(other)
172
+ self.class == other.class && state == other.state
173
+ end
174
+
175
+ alias_method :eql?, :==
176
+
177
+ protected
178
+
179
+ def state
180
+ [domain, token, api_version, extra]
181
+ end
182
+
130
183
  private
131
- def parameterize(params)
132
- URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))
133
- end
134
184
 
135
- def access_token_request(code)
136
- uri = URI.parse("#{protocol}://#{url}/admin/oauth/access_token")
137
- https = Net::HTTP.new(uri.host, uri.port)
138
- https.use_ssl = true
139
- request = Net::HTTP::Post.new(uri.request_uri)
140
- request.set_form_data({"client_id" => api_key, "client_secret" => secret, "code" => code})
141
- https.request(request)
142
- end
185
+ def access_scopes=(access_scopes)
186
+ return unless access_scopes
187
+ @access_scopes = ShopifyAPI::ApiAccess.new(access_scopes)
188
+ end
189
+
190
+ def parameterize(params)
191
+ URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))
192
+ end
193
+
194
+ def access_token_request(code)
195
+ uri = URI.parse(construct_oauth_url('access_token'))
196
+ https = Net::HTTP.new(uri.host, uri.port)
197
+ https.use_ssl = true
198
+ request = Net::HTTP::Post.new(uri.request_uri)
199
+ request.set_form_data('client_id' => api_key, 'client_secret' => secret, 'code' => code)
200
+ https.request(request)
201
+ end
202
+
203
+ def construct_oauth_url(path, query_params = {})
204
+ query_string = "?#{parameterize(query_params)}" unless query_params.empty?
205
+ "https://#{domain}/admin/oauth/#{path}#{query_string}"
206
+ end
143
207
  end
144
208
  end