ruby_shopify_api 1.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 (351) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/CODEOWNERS +1 -0
  4. data/.github/ISSUE_TEMPLATE.md +36 -0
  5. data/.github/probots.yml +2 -0
  6. data/.github/workflows/build.yml +43 -0
  7. data/.gitignore +15 -0
  8. data/.rubocop.yml +28 -0
  9. data/.rubocop_todo.yml +75 -0
  10. data/CHANGELOG-OLD.md +501 -0
  11. data/CHANGELOG.md +5 -0
  12. data/CONTRIBUTING.md +8 -0
  13. data/CONTRIBUTORS +3 -0
  14. data/Gemfile +10 -0
  15. data/Gemfile.lock +151 -0
  16. data/Gemfile_ar41 +5 -0
  17. data/Gemfile_ar50 +5 -0
  18. data/Gemfile_ar51 +5 -0
  19. data/Gemfile_ar60 +5 -0
  20. data/Gemfile_ar_main +5 -0
  21. data/LICENSE +20 -0
  22. data/README.md +649 -0
  23. data/RELEASING +17 -0
  24. data/Rakefile +55 -0
  25. data/SECURITY.md +59 -0
  26. data/dev.yml +11 -0
  27. data/docker-compose.yml +13 -0
  28. data/docs/_config.yml +1 -0
  29. data/docs/_includes/footer.html +28 -0
  30. data/docs/_includes/head.html +28 -0
  31. data/docs/_layouts/index.html +57 -0
  32. data/docs/graphql.md +241 -0
  33. data/docs/index.md +639 -0
  34. data/lib/active_resource/connection_ext.rb +11 -0
  35. data/lib/active_resource/detailed_log_subscriber.rb +55 -0
  36. data/lib/active_resource/json_errors.rb +37 -0
  37. data/lib/shopify_api/api_access.rb +57 -0
  38. data/lib/shopify_api/api_version.rb +206 -0
  39. data/lib/shopify_api/connection.rb +36 -0
  40. data/lib/shopify_api/countable.rb +15 -0
  41. data/lib/shopify_api/disable_prefix_check.rb +31 -0
  42. data/lib/shopify_api/events.rb +8 -0
  43. data/lib/shopify_api/graphql/http_client.rb +22 -0
  44. data/lib/shopify_api/graphql/railtie.rb +17 -0
  45. data/lib/shopify_api/graphql/task.rake +100 -0
  46. data/lib/shopify_api/graphql.rb +103 -0
  47. data/lib/shopify_api/hmac_params.rb +33 -0
  48. data/lib/shopify_api/limits.rb +77 -0
  49. data/lib/shopify_api/message_enricher.rb +25 -0
  50. data/lib/shopify_api/meta.rb +14 -0
  51. data/lib/shopify_api/metafields.rb +21 -0
  52. data/lib/shopify_api/paginated_collection.rb +69 -0
  53. data/lib/shopify_api/pagination_link_headers.rb +34 -0
  54. data/lib/shopify_api/resources/abandoned_checkout.rb +7 -0
  55. data/lib/shopify_api/resources/access_scope.rb +10 -0
  56. data/lib/shopify_api/resources/access_token.rb +9 -0
  57. data/lib/shopify_api/resources/address.rb +5 -0
  58. data/lib/shopify_api/resources/announcement.rb +5 -0
  59. data/lib/shopify_api/resources/api_permission.rb +9 -0
  60. data/lib/shopify_api/resources/application_charge.rb +16 -0
  61. data/lib/shopify_api/resources/application_credit.rb +5 -0
  62. data/lib/shopify_api/resources/array_base.rb +13 -0
  63. data/lib/shopify_api/resources/article.rb +22 -0
  64. data/lib/shopify_api/resources/asset.rb +101 -0
  65. data/lib/shopify_api/resources/assigned_fulfillment_order.rb +16 -0
  66. data/lib/shopify_api/resources/base.rb +166 -0
  67. data/lib/shopify_api/resources/billing_address.rb +5 -0
  68. data/lib/shopify_api/resources/blog.rb +11 -0
  69. data/lib/shopify_api/resources/carrier_service.rb +5 -0
  70. data/lib/shopify_api/resources/cart.rb +5 -0
  71. data/lib/shopify_api/resources/checkout.rb +30 -0
  72. data/lib/shopify_api/resources/collect.rb +7 -0
  73. data/lib/shopify_api/resources/collection.rb +14 -0
  74. data/lib/shopify_api/resources/collection_listing.rb +19 -0
  75. data/lib/shopify_api/resources/collection_publication.rb +10 -0
  76. data/lib/shopify_api/resources/comment.rb +24 -0
  77. data/lib/shopify_api/resources/country.rb +5 -0
  78. data/lib/shopify_api/resources/currency.rb +6 -0
  79. data/lib/shopify_api/resources/custom_collection.rb +20 -0
  80. data/lib/shopify_api/resources/customer.rb +30 -0
  81. data/lib/shopify_api/resources/customer_group.rb +6 -0
  82. data/lib/shopify_api/resources/customer_invite.rb +5 -0
  83. data/lib/shopify_api/resources/customer_saved_search.rb +12 -0
  84. data/lib/shopify_api/resources/discount_code.rb +10 -0
  85. data/lib/shopify_api/resources/discount_code_batch.rb +34 -0
  86. data/lib/shopify_api/resources/draft_order.rb +15 -0
  87. data/lib/shopify_api/resources/draft_order_invoice.rb +5 -0
  88. data/lib/shopify_api/resources/event.rb +9 -0
  89. data/lib/shopify_api/resources/fulfillment.rb +56 -0
  90. data/lib/shopify_api/resources/fulfillment_event.rb +16 -0
  91. data/lib/shopify_api/resources/fulfillment_order.rb +151 -0
  92. data/lib/shopify_api/resources/fulfillment_order_locations_for_move.rb +5 -0
  93. data/lib/shopify_api/resources/fulfillment_request.rb +16 -0
  94. data/lib/shopify_api/resources/fulfillment_service.rb +5 -0
  95. data/lib/shopify_api/resources/fulfillment_v2.rb +21 -0
  96. data/lib/shopify_api/resources/gift_card.rb +8 -0
  97. data/lib/shopify_api/resources/image.rb +17 -0
  98. data/lib/shopify_api/resources/inventory_item.rb +6 -0
  99. data/lib/shopify_api/resources/inventory_level.rb +54 -0
  100. data/lib/shopify_api/resources/line_item.rb +15 -0
  101. data/lib/shopify_api/resources/location.rb +8 -0
  102. data/lib/shopify_api/resources/marketing_event.rb +11 -0
  103. data/lib/shopify_api/resources/metafield.rb +14 -0
  104. data/lib/shopify_api/resources/note_attribute.rb +5 -0
  105. data/lib/shopify_api/resources/option.rb +5 -0
  106. data/lib/shopify_api/resources/order.rb +44 -0
  107. data/lib/shopify_api/resources/order_risk.rb +9 -0
  108. data/lib/shopify_api/resources/page.rb +7 -0
  109. data/lib/shopify_api/resources/payment.rb +7 -0
  110. data/lib/shopify_api/resources/payment_details.rb +5 -0
  111. data/lib/shopify_api/resources/ping.rb +3 -0
  112. data/lib/shopify_api/resources/policy.rb +8 -0
  113. data/lib/shopify_api/resources/price_rule.rb +8 -0
  114. data/lib/shopify_api/resources/product.rb +59 -0
  115. data/lib/shopify_api/resources/product_listing.rb +17 -0
  116. data/lib/shopify_api/resources/product_publication.rb +10 -0
  117. data/lib/shopify_api/resources/province.rb +6 -0
  118. data/lib/shopify_api/resources/publication.rb +5 -0
  119. data/lib/shopify_api/resources/receipt.rb +5 -0
  120. data/lib/shopify_api/resources/recurring_application_charge.rb +34 -0
  121. data/lib/shopify_api/resources/redirect.rb +5 -0
  122. data/lib/shopify_api/resources/refund.rb +15 -0
  123. data/lib/shopify_api/resources/report.rb +5 -0
  124. data/lib/shopify_api/resources/resource_feedback.rb +19 -0
  125. data/lib/shopify_api/resources/rule.rb +5 -0
  126. data/lib/shopify_api/resources/script_tag.rb +5 -0
  127. data/lib/shopify_api/resources/shipping_address.rb +5 -0
  128. data/lib/shopify_api/resources/shipping_line.rb +5 -0
  129. data/lib/shopify_api/resources/shipping_rate.rb +7 -0
  130. data/lib/shopify_api/resources/shipping_zone.rb +5 -0
  131. data/lib/shopify_api/resources/shop.rb +26 -0
  132. data/lib/shopify_api/resources/smart_collection.rb +15 -0
  133. data/lib/shopify_api/resources/storefront_access_token.rb +5 -0
  134. data/lib/shopify_api/resources/tax_line.rb +5 -0
  135. data/lib/shopify_api/resources/tax_service.rb +5 -0
  136. data/lib/shopify_api/resources/tender_transaction.rb +6 -0
  137. data/lib/shopify_api/resources/theme.rb +5 -0
  138. data/lib/shopify_api/resources/transaction.rb +6 -0
  139. data/lib/shopify_api/resources/usage_charge.rb +6 -0
  140. data/lib/shopify_api/resources/user.rb +5 -0
  141. data/lib/shopify_api/resources/variant.rb +43 -0
  142. data/lib/shopify_api/resources/webhook.rb +5 -0
  143. data/lib/shopify_api/resources.rb +4 -0
  144. data/lib/shopify_api/session.rb +203 -0
  145. data/lib/shopify_api/version.rb +4 -0
  146. data/lib/shopify_api.rb +45 -0
  147. data/lib/verify_docs.rb +8 -0
  148. data/service.yml +2 -0
  149. data/shipit.rubygems.yml +1 -0
  150. data/shopify_api.gemspec +47 -0
  151. data/test/abandoned_checkouts_test.rb +29 -0
  152. data/test/access_scope_test.rb +23 -0
  153. data/test/access_token_test.rb +20 -0
  154. data/test/active_resource/json_errors_test.rb +19 -0
  155. data/test/api_access_test.rb +153 -0
  156. data/test/api_permission_test.rb +9 -0
  157. data/test/api_version_test.rb +157 -0
  158. data/test/application_charge_test.rb +82 -0
  159. data/test/application_credit_test.rb +36 -0
  160. data/test/article_test.rb +72 -0
  161. data/test/asset_test.rb +26 -0
  162. data/test/assigned_fulfillment_order_test.rb +78 -0
  163. data/test/base_test.rb +213 -0
  164. data/test/blog_test.rb +9 -0
  165. data/test/carrier_service_test.rb +18 -0
  166. data/test/cart_test.rb +14 -0
  167. data/test/checkouts_test.rb +77 -0
  168. data/test/collect_test.rb +10 -0
  169. data/test/collection_listing_test.rb +84 -0
  170. data/test/collection_publication_test.rb +40 -0
  171. data/test/collection_test.rb +50 -0
  172. data/test/countable_test.rb +14 -0
  173. data/test/currency_test.rb +21 -0
  174. data/test/custom_collection_test.rb +10 -0
  175. data/test/customer_saved_search_test.rb +37 -0
  176. data/test/customer_test.rb +58 -0
  177. data/test/detailed_log_subscriber_test.rb +143 -0
  178. data/test/discount_code_batch_test.rb +41 -0
  179. data/test/discount_code_test.rb +59 -0
  180. data/test/draft_order_test.rb +167 -0
  181. data/test/fixtures/abandoned_checkout.json +184 -0
  182. data/test/fixtures/abandoned_checkouts.json +186 -0
  183. data/test/fixtures/access_scopes.json +10 -0
  184. data/test/fixtures/access_token_delegate.json +4 -0
  185. data/test/fixtures/api_versions.json +38 -0
  186. data/test/fixtures/apis.json +42 -0
  187. data/test/fixtures/application_charge.json +16 -0
  188. data/test/fixtures/application_charges.json +57 -0
  189. data/test/fixtures/application_credit.json +12 -0
  190. data/test/fixtures/application_credits.json +24 -0
  191. data/test/fixtures/article.json +15 -0
  192. data/test/fixtures/articles.json +39 -0
  193. data/test/fixtures/asset.json +9 -0
  194. data/test/fixtures/assets.json +136 -0
  195. data/test/fixtures/assigned_fulfillment_orders.json +80 -0
  196. data/test/fixtures/authors.json +1 -0
  197. data/test/fixtures/blog.json +13 -0
  198. data/test/fixtures/blogs.json +13 -0
  199. data/test/fixtures/carrier_service.json +9 -0
  200. data/test/fixtures/carts.json +43 -0
  201. data/test/fixtures/checkout.json +160 -0
  202. data/test/fixtures/checkouts.json +162 -0
  203. data/test/fixtures/collect.json +12 -0
  204. data/test/fixtures/collection.json +17 -0
  205. data/test/fixtures/collection_listing.json +11 -0
  206. data/test/fixtures/collection_listing_product_ids.json +1 -0
  207. data/test/fixtures/collection_listing_product_ids2.json +1 -0
  208. data/test/fixtures/collection_listings.json +13 -0
  209. data/test/fixtures/collection_products.json +47 -0
  210. data/test/fixtures/collection_publication.json +11 -0
  211. data/test/fixtures/collection_publications.json +13 -0
  212. data/test/fixtures/currencies.json +25 -0
  213. data/test/fixtures/custom_collection.json +17 -0
  214. data/test/fixtures/customer_invite.json +9 -0
  215. data/test/fixtures/customer_saved_search.json +9 -0
  216. data/test/fixtures/customer_saved_search_customers.json +60 -0
  217. data/test/fixtures/customers.json +59 -0
  218. data/test/fixtures/customers_account_activation_url.json +3 -0
  219. data/test/fixtures/customers_search.json +60 -0
  220. data/test/fixtures/discount_code.json +10 -0
  221. data/test/fixtures/discount_code_batch.json +14 -0
  222. data/test/fixtures/discount_code_batch_discount_codes.json +21 -0
  223. data/test/fixtures/discount_codes.json +12 -0
  224. data/test/fixtures/draft_order.json +159 -0
  225. data/test/fixtures/draft_order_completed.json +159 -0
  226. data/test/fixtures/draft_order_invoice.json +9 -0
  227. data/test/fixtures/draft_orders.json +161 -0
  228. data/test/fixtures/engagement.json +15 -0
  229. data/test/fixtures/events.json +31 -0
  230. data/test/fixtures/fulfillment.json +49 -0
  231. data/test/fixtures/fulfillment_event.json +12 -0
  232. data/test/fixtures/fulfillment_order.json +39 -0
  233. data/test/fixtures/fulfillment_order_locations_for_move.json +18 -0
  234. data/test/fixtures/fulfillment_orders.json +80 -0
  235. data/test/fixtures/fulfillment_request.json +28 -0
  236. data/test/fixtures/fulfillment_service.json +10 -0
  237. data/test/fixtures/fulfillments.json +53 -0
  238. data/test/fixtures/gift_card.json +20 -0
  239. data/test/fixtures/gift_card_disabled.json +20 -0
  240. data/test/fixtures/graphql/2019-10.json +1083 -0
  241. data/test/fixtures/graphql/dummy_schema.rb +16 -0
  242. data/test/fixtures/graphql/unstable.json +1083 -0
  243. data/test/fixtures/image.json +10 -0
  244. data/test/fixtures/images.json +20 -0
  245. data/test/fixtures/inventory_level.json +7 -0
  246. data/test/fixtures/inventory_levels.json +24 -0
  247. data/test/fixtures/marketing_event.json +28 -0
  248. data/test/fixtures/marketing_events.json +54 -0
  249. data/test/fixtures/metafield.json +12 -0
  250. data/test/fixtures/metafields.json +34 -0
  251. data/test/fixtures/order.json +297 -0
  252. data/test/fixtures/order_risk.json +14 -0
  253. data/test/fixtures/order_risks.json +28 -0
  254. data/test/fixtures/order_with_properties.json +373 -0
  255. data/test/fixtures/orders.json +299 -0
  256. data/test/fixtures/payment.json +7 -0
  257. data/test/fixtures/payments.json +9 -0
  258. data/test/fixtures/ping/conversation.json +1 -0
  259. data/test/fixtures/ping/failed_delivery_confirmation.json +1 -0
  260. data/test/fixtures/ping/message.json +1 -0
  261. data/test/fixtures/ping/successful_delivery_confirmation.json +1 -0
  262. data/test/fixtures/policies.json +8 -0
  263. data/test/fixtures/price_rule.json +27 -0
  264. data/test/fixtures/price_rules.json +28 -0
  265. data/test/fixtures/product.json +116 -0
  266. data/test/fixtures/product_listing.json +86 -0
  267. data/test/fixtures/product_listing_product_ids.json +1 -0
  268. data/test/fixtures/product_listing_product_ids2.json +1 -0
  269. data/test/fixtures/product_listings.json +174 -0
  270. data/test/fixtures/product_publication.json +11 -0
  271. data/test/fixtures/product_publications.json +13 -0
  272. data/test/fixtures/publications.json +9 -0
  273. data/test/fixtures/recurring_application_charge.json +22 -0
  274. data/test/fixtures/recurring_application_charge_adjustment.json +5 -0
  275. data/test/fixtures/recurring_application_charges.json +106 -0
  276. data/test/fixtures/redirect.json +7 -0
  277. data/test/fixtures/refund.json +112 -0
  278. data/test/fixtures/report.json +9 -0
  279. data/test/fixtures/reports.json +11 -0
  280. data/test/fixtures/script_tag.json +10 -0
  281. data/test/fixtures/script_tags.json +18 -0
  282. data/test/fixtures/shipping_rates.json +12 -0
  283. data/test/fixtures/shipping_zones.json +315 -0
  284. data/test/fixtures/shop.json +26 -0
  285. data/test/fixtures/smart_collection.json +21 -0
  286. data/test/fixtures/smart_collection_products.json +155 -0
  287. data/test/fixtures/storefront_access_token.json +9 -0
  288. data/test/fixtures/storefront_access_tokens.json +18 -0
  289. data/test/fixtures/tags.json +1 -0
  290. data/test/fixtures/tax_service.json +9 -0
  291. data/test/fixtures/tender_transactions.json +52 -0
  292. data/test/fixtures/transaction.json +29 -0
  293. data/test/fixtures/usage_charge.json +11 -0
  294. data/test/fixtures/usage_charges.json +23 -0
  295. data/test/fixtures/user.json +21 -0
  296. data/test/fixtures/users.json +42 -0
  297. data/test/fixtures/variant.json +23 -0
  298. data/test/fixtures/variants.json +88 -0
  299. data/test/fixtures/webhook.json +10 -0
  300. data/test/fixtures/webhooks.json +18 -0
  301. data/test/fulfillment_event_test.rb +74 -0
  302. data/test/fulfillment_order_test.rb +530 -0
  303. data/test/fulfillment_order_test_helper.rb +8 -0
  304. data/test/fulfillment_request_test.rb +35 -0
  305. data/test/fulfillment_service_test.rb +18 -0
  306. data/test/fulfillment_test.rb +239 -0
  307. data/test/fulfillment_v2_test.rb +66 -0
  308. data/test/gift_card_test.rb +24 -0
  309. data/test/graphql/http_client_test.rb +26 -0
  310. data/test/graphql_test.rb +190 -0
  311. data/test/hmac_params_test.rb +25 -0
  312. data/test/image_test.rb +41 -0
  313. data/test/inventory_level_test.rb +68 -0
  314. data/test/lib/webmock_extensions/last_request.rb +16 -0
  315. data/test/limits_test.rb +39 -0
  316. data/test/location_test.rb +15 -0
  317. data/test/marketing_event_test.rb +68 -0
  318. data/test/message_enricher_test.rb +45 -0
  319. data/test/meta_test.rb +47 -0
  320. data/test/metafield_test.rb +56 -0
  321. data/test/order_risk_test.rb +47 -0
  322. data/test/order_test.rb +140 -0
  323. data/test/pagination_test.rb +290 -0
  324. data/test/payment_test.rb +19 -0
  325. data/test/policy_test.rb +20 -0
  326. data/test/price_rule_test.rb +70 -0
  327. data/test/product_listing_test.rb +97 -0
  328. data/test/product_publication_test.rb +40 -0
  329. data/test/product_test.rb +111 -0
  330. data/test/publication_test.rb +12 -0
  331. data/test/recurring_application_charge_test.rb +204 -0
  332. data/test/redirect_test.rb +10 -0
  333. data/test/refund_test.rb +37 -0
  334. data/test/report_test.rb +37 -0
  335. data/test/resource_feedback_test.rb +43 -0
  336. data/test/script_tag_test.rb +31 -0
  337. data/test/session_test.rb +640 -0
  338. data/test/shipping_rate_test.rb +17 -0
  339. data/test/shipping_zone_test.rb +11 -0
  340. data/test/shop_test.rb +82 -0
  341. data/test/smart_collection_test.rb +11 -0
  342. data/test/storefront_access_token_test.rb +30 -0
  343. data/test/tax_service_test.rb +12 -0
  344. data/test/tender_transaction_test.rb +18 -0
  345. data/test/test_helper.rb +126 -0
  346. data/test/transaction_test.rb +18 -0
  347. data/test/usage_charge_test.rb +25 -0
  348. data/test/user_test.rb +18 -0
  349. data/test/variant_test.rb +73 -0
  350. data/test/webhook_test.rb +24 -0
  351. metadata +555 -0
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ module HmacParams
5
+ class << self
6
+ def encode(params)
7
+ params
8
+ .except(:signature, :hmac, :action, :controller)
9
+ .map { |k,v| sprintf("%s=%s", encode_key(k), encode_value(v)) }
10
+ .sort.join("&")
11
+ end
12
+
13
+ KEY_REGEXP = /([#{Regexp.escape("&=%")}])/n
14
+ def encode_key(key)
15
+ _escape(key.to_s, KEY_REGEXP)
16
+ end
17
+
18
+ VALUE_REGEXP = /([#{Regexp.escape("&%")}])/n
19
+ def encode_value(value)
20
+ _escape(value.to_s, VALUE_REGEXP)
21
+ end
22
+
23
+ private
24
+
25
+ def _escape(str, regex)
26
+ str = str.b
27
+ str.gsub!(regex) {"%%%02X" % $1.ord}
28
+ # %-escaped string should contain US-ASCII only
29
+ str.force_encoding(Encoding::US_ASCII)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ module Limits
4
+ class LimitUnavailable < StandardError; end
5
+
6
+ def self.included(klass)
7
+ klass.send(:extend, ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ # Takes form <call count>/<bucket size>
12
+ # See https://help.shopify.com/en/api/getting-started/api-call-limit
13
+ # Eg: 2/40
14
+ CREDIT_LIMIT_HEADER_PARAM = {
15
+ shop: 'X-Shopify-Shop-Api-Call-Limit',
16
+ }
17
+
18
+ ##
19
+ # How many more API calls can I make?
20
+ # @return {Integer}
21
+ #
22
+ def credit_left
23
+ credit_limit(:shop) - credit_used(:shop)
24
+ end
25
+ alias_method :available_calls, :credit_left
26
+
27
+ ##
28
+ # Have I reached my API call limit?
29
+ # @return {Boolean}
30
+ #
31
+ def credit_maxed?
32
+ credit_left <= 0
33
+ end
34
+ alias_method :maxed?, :credit_maxed?
35
+
36
+ ##
37
+ # How many total API calls can I make?
38
+ # NOTE: subtracting 1 from credit_limit because I think ShopifyAPI cuts off at 299 or shop limits.
39
+ # @param {Symbol} scope [:shop]
40
+ # @return {Integer}
41
+ #
42
+ def credit_limit(scope = :shop)
43
+ api_credit_limit_param(scope).pop.to_i - 1
44
+ end
45
+ alias_method :call_limit, :credit_limit
46
+
47
+ ##
48
+ # How many API calls have I made?
49
+ # @param {Symbol} scope [:shop]
50
+ # @return {Integer}
51
+ #
52
+ def credit_used(scope = :shop)
53
+ api_credit_limit_param(scope).shift.to_i
54
+ end
55
+ alias_method :call_count, :credit_used
56
+
57
+ ##
58
+ # @return {HTTPResonse}
59
+ #
60
+ def response
61
+ Shop.current unless ShopifyAPI::Base.connection.response
62
+ ShopifyAPI::Base.connection.response
63
+ end
64
+
65
+ private
66
+
67
+ ##
68
+ # @return {Array}
69
+ #
70
+ def api_credit_limit_param(scope)
71
+ header = response[CREDIT_LIMIT_HEADER_PARAM[scope]]
72
+ raise LimitUnavailable unless header
73
+ header.split('/')
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class MessageEnricher < SimpleDelegator
4
+ def message
5
+ return super unless (400...500).include?(code.to_i)
6
+
7
+ @_cached_message ||= begin
8
+ detailed_error =
9
+ begin
10
+ parsed_body = JSON.parse(body)
11
+
12
+ if parsed_body['error']
13
+ parsed_body['error'].to_s
14
+ elsif parsed_body['errors']
15
+ Array(parsed_body['errors']).join('; ')
16
+ end
17
+ rescue JSON::ParserError
18
+ nil
19
+ end
20
+
21
+ detailed_error.present? ? "#{super} (#{detailed_error})" : super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+
2
+ # frozen_string_literal: true
3
+ module ShopifyAPI
4
+ class Meta < ActiveResource::Base
5
+ self.site = "https://app.shopify.com/services/"
6
+ self.element_name = 'api'
7
+ self.primary_key = :handle
8
+ self.timeout = 5
9
+
10
+ def self.admin_versions
11
+ all.find { |api| api.handle = :admin }.versions
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ module Metafields
4
+ def metafields(**options)
5
+ options.merge!(resource: self.class.collection_name, resource_id: id)
6
+
7
+ Metafield.find(:all, params: options)
8
+ end
9
+
10
+ def add_metafield(metafield)
11
+ raise ArgumentError, "You can only add metafields to resource that has been saved" if new?
12
+
13
+ metafield.prefix_options = {
14
+ resource: self.class.collection_name,
15
+ resource_id: id,
16
+ }
17
+ metafield.save
18
+ metafield
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class PaginatedCollection < ActiveResource::Collection
5
+ module CollectionPagination
6
+ def initialize(args)
7
+ @next_url = pagination_link_headers.next_link&.url&.to_s
8
+ @previous_url = pagination_link_headers.previous_link&.url&.to_s
9
+ super(args)
10
+ end
11
+
12
+ def next_page?
13
+ ensure_available
14
+ @next_url.present?
15
+ end
16
+
17
+ def previous_page?
18
+ ensure_available
19
+ @previous_url.present?
20
+ end
21
+
22
+ def fetch_next_page
23
+ fetch_page(@next_url)
24
+ end
25
+
26
+ def fetch_previous_page
27
+ fetch_page(@previous_url)
28
+ end
29
+
30
+ def next_page_info
31
+ extract_page_info(@next_url)
32
+ end
33
+
34
+ def previous_page_info
35
+ extract_page_info(@previous_url)
36
+ end
37
+
38
+ private
39
+
40
+ AVAILABLE_IN_VERSION = ShopifyAPI::ApiVersion.find_version('2019-10')
41
+ AVAILABLE_IN_VERSION_EARLY = ShopifyAPI::ApiVersion.find_version('2019-07')
42
+
43
+ def fetch_page(url)
44
+ ensure_available
45
+ return [] unless url.present?
46
+
47
+ resource_class.all(from: url)
48
+ end
49
+
50
+ def pagination_link_headers
51
+ @pagination_link_headers ||= ShopifyAPI::PaginationLinkHeaders.new(
52
+ ShopifyAPI::Base.connection.response["Link"]
53
+ )
54
+ end
55
+
56
+ def ensure_available
57
+ return if ShopifyAPI::Base.api_version >= AVAILABLE_IN_VERSION
58
+ return if ShopifyAPI::Base.api_version >= AVAILABLE_IN_VERSION_EARLY && resource_class.early_july_pagination?
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def extract_page_info(url)
63
+ CGI.escape(Rack::Utils.parse_query(URI(url).query)['page_info']) if url.present?
64
+ end
65
+ end
66
+
67
+ include CollectionPagination
68
+ end
69
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class InvalidPaginationLinksError < StandardError; end
4
+
5
+ class PaginationLinkHeaders
6
+ LinkHeader = Struct.new(:url, :rel)
7
+ attr_reader :previous_link, :next_link
8
+
9
+ def initialize(link_header)
10
+ links = parse_link_header(link_header)
11
+ @previous_link = links.find { |link| link.rel == :previous }
12
+ @next_link = links.find { |link| link.rel == :next }
13
+ end
14
+
15
+ private
16
+
17
+ def parse_link_header(link_header)
18
+ return [] unless link_header.present?
19
+ links = link_header.split(',')
20
+ links.map do |link|
21
+ parts = link.split('; ')
22
+ unless parts.length == 2
23
+ raise ShopifyAPI::InvalidPaginationLinksError, "Invalid link header: url and rel expected"
24
+ end
25
+
26
+ url = parts[0][/<(.*)>/, 1]
27
+ rel = parts[1][/rel="(.*)"/, 1]&.to_sym
28
+
29
+ url = URI.parse(url).request_uri
30
+ LinkHeader.new(url, rel)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class AbandonedCheckout < Base
5
+ self.element_name = "checkout"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class AccessScope < Base
4
+ class << self
5
+ def prefix(_options = {})
6
+ '/admin/oauth/'
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class AccessToken < Base
4
+ def self.delegate(access_scope, expires_in = nil)
5
+ resource = post(:delegate, delegate_access_scope: access_scope, expires_in: expires_in)
6
+ instantiate_record(format.decode(resource.body), {})
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class Address < Base
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class Announcement < Base
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class ApiPermission < Base
5
+ def self.destroy
6
+ delete(:current)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class ApplicationCharge < Base
4
+ undef_method :test
5
+
6
+ class << self
7
+ [:pending, :expired, :accepted, :declined].each do |status|
8
+ define_method(status) { (all || []).select { |c| c.status == status.to_s } }
9
+ end
10
+ end
11
+
12
+ def activate
13
+ load_attributes_from_response(post(:activate))
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class ApplicationCredit < Base
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ class ArrayBase < Base
5
+ class << self
6
+ private
7
+
8
+ def instantiate_record(record, *)
9
+ record
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class Article < Base
4
+ include Events
5
+ include Metafields
6
+ include DisablePrefixCheck
7
+
8
+ conditional_prefix :blog
9
+
10
+ def comments
11
+ Comment.find(:all, params: { article_id: id })
12
+ end
13
+
14
+ def self.authors(options = {})
15
+ get(:authors, options)
16
+ end
17
+
18
+ def self.tags(options = {})
19
+ get(:tags, options)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ # Assets represent the files that comprise your theme.
4
+ # There are different buckets which hold different kinds
5
+ # of assets, each corresponding to one of the folders
6
+ # within a theme's zip file: "layout", "templates",
7
+ # "snippets", "assets", and "config". The full key of an
8
+ # asset always starts with the bucket name, and the path
9
+ # separator is a forward slash, like layout/theme.liquid
10
+ # or assets/bg-body.gif.
11
+ #
12
+ # Initialize with a key:
13
+ # asset = ShopifyAPI::Asset.new(key: 'assets/special.css', theme_id: 12345)
14
+ #
15
+ # Find by key:
16
+ # asset = ShopifyAPI::Asset.find('assets/image.png', params: {theme_id: 12345})
17
+ #
18
+ # Get the text or binary value:
19
+ # asset.value # decodes from attachment attribute if necessary
20
+ #
21
+ # You can provide new data for assets in a few different ways:
22
+ #
23
+ # * assign text data for the value directly:
24
+ # asset.value = "div.special {color:red;}"
25
+ #
26
+ # * provide binary data for the value:
27
+ # asset.attach(File.read('image.png'))
28
+ #
29
+ # * set a URL from which Shopify will fetch the value:
30
+ # asset.src = "http://mysite.com/image.png"
31
+ #
32
+ # * set a source key of another of your assets from which
33
+ # the value will be copied:
34
+ # asset.source_key = "assets/another_image.png"
35
+ class Asset < Base
36
+ include DisablePrefixCheck
37
+
38
+ self.primary_key = 'key'
39
+
40
+ conditional_prefix :theme
41
+
42
+ def self.element_path(_id, prefix_options = {}, query_options = nil) #:nodoc:
43
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
44
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
45
+ end
46
+
47
+ # find an asset by key:
48
+ # ShopifyAPI::Asset.find('layout/theme.liquid', params: { theme_id: 99 })
49
+ def self.find(*args)
50
+ if args[0].is_a?(Symbol)
51
+ super
52
+ else
53
+ params = { asset: { key: args[0] } }
54
+ params = params.merge(args[1][:params]) if args[1] && args[1][:params]
55
+ path_prefix = params[:theme_id] ? "themes/#{params[:theme_id]}/" : ""
56
+ resource = find(
57
+ :one,
58
+ from: api_version.construct_api_path("#{path_prefix}assets.#{format.extension}"),
59
+ params: params
60
+ )
61
+ resource.prefix_options[:theme_id] = params[:theme_id] if resource && params[:theme_id]
62
+ resource
63
+ end
64
+ end
65
+
66
+ # For text assets, Shopify returns the data in the 'value' attribute.
67
+ # For binary assets, the data is base-64-encoded and returned in the
68
+ # 'attachment' attribute. This accessor returns the data in both cases.
69
+ def value
70
+ attributes['value'] ||
71
+ (attributes['attachment'] ? Base64.decode64(attributes['attachment']) : nil)
72
+ end
73
+
74
+ def attach(data)
75
+ self.attachment = Base64.encode64(data)
76
+ end
77
+
78
+ def destroy
79
+ connection.delete(element_path(prefix_options.merge(asset: { key: key })), self.class.headers)
80
+ end
81
+
82
+ def new?
83
+ false
84
+ end
85
+
86
+ def method_missing(method_symbol, *arguments) #:nodoc:
87
+ if %w{value= attachment= src= source_key=}.include?(method_symbol)
88
+ wipe_value_attributes
89
+ end
90
+ super
91
+ end
92
+
93
+ private
94
+
95
+ def wipe_value_attributes
96
+ %w{value attachment src source_key}.each do |attr|
97
+ attributes.delete(attr)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class AssignedFulfillmentOrder < Base
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
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_api/version'
3
+
4
+ module ShopifyAPI
5
+ class Base < ActiveResource::Base
6
+ class InvalidSessionError < StandardError; end
7
+ extend Countable
8
+
9
+ self.timeout = 90
10
+ self.include_root_in_json = false
11
+ self.headers['User-Agent'] = ["RubyShopifyAPI/#{ShopifyAPI::VERSION}",
12
+ "ActiveResource/#{ActiveResource::VERSION::STRING}",
13
+ "Ruby/#{RUBY_VERSION}"].join(' ')
14
+
15
+ self.collection_parser = ShopifyAPI::PaginatedCollection
16
+
17
+ def encode(options = {})
18
+ same = dup
19
+ same.attributes = { self.class.element_name => same.attributes } if self.class.format.extension == 'json'
20
+
21
+ same.send("to_#{self.class.format.extension}", options)
22
+ end
23
+
24
+ def as_json(options = nil)
25
+ root = options[:root] if options.try(:key?, :root)
26
+ if include_root_in_json
27
+ root = self.class.model_name.element if root == true
28
+ { root => serializable_hash(options) }
29
+ else
30
+ serializable_hash(options)
31
+ end
32
+ end
33
+
34
+ class << self
35
+ threadsafe_attribute(:_api_version)
36
+ def headers
37
+ if _headers_defined?
38
+ _headers
39
+ elsif superclass != Object && superclass.headers
40
+ superclass.headers
41
+ else
42
+ _headers ||= {}
43
+ end
44
+ end
45
+
46
+ def activate_session(session)
47
+ raise InvalidSessionError, "Session cannot be nil" if session.nil?
48
+ self.site = session.site
49
+ self.headers.merge!('X-Shopify-Access-Token' => session.token)
50
+ self.api_version = session.api_version
51
+ end
52
+
53
+ def clear_session
54
+ self.site = nil
55
+ self.password = nil
56
+ self.user = nil
57
+ self.headers.delete('X-Shopify-Access-Token')
58
+ end
59
+
60
+ def api_version
61
+ if _api_version_defined?
62
+ _api_version
63
+ elsif superclass != Object && superclass.site
64
+ superclass.api_version.dup.freeze
65
+ else
66
+ ApiVersion::NullVersion
67
+ end
68
+ end
69
+
70
+ def api_version=(version)
71
+ self._api_version = if ApiVersion::NullVersion.matches?(version)
72
+ ApiVersion::NullVersion
73
+ else
74
+ ApiVersion.find_version(version)
75
+ end
76
+ end
77
+
78
+ def prefix(options = {})
79
+ api_version.construct_api_path(resource_prefix(options))
80
+ end
81
+
82
+ def prefix_source
83
+ ''
84
+ end
85
+
86
+ def resource_prefix(_options = {})
87
+ ''
88
+ end
89
+
90
+ # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
91
+ # Default value is <tt>site.path</tt>.
92
+ def resource_prefix=(value)
93
+ @prefix_parameters = nil
94
+
95
+ resource_prefix_call = value.gsub(/:\w+/) { |key| "\#{URI::DEFAULT_PARSER.escape options[#{key}].to_s}" }
96
+
97
+ silence_warnings do
98
+ # Redefine the new methods.
99
+ instance_eval(<<-RUBY_EVAL, __FILE__, __LINE__ + 1)
100
+ def prefix_source() "#{value}" end
101
+ def resource_prefix(options={}) "#{resource_prefix_call}" end
102
+ RUBY_EVAL
103
+ end
104
+ rescue => e
105
+ logger&.error("Couldn't set prefix: #{e}\n #{code}")
106
+ raise
107
+ end
108
+
109
+ def prefix=(value)
110
+ if value.start_with?('/admin')
111
+ raise ArgumentError, "'#{value}' can no longer start /admin/. Change to using resource_prefix="
112
+ end
113
+
114
+ warn(
115
+ '[DEPRECATED] ShopifyAPI::Base#prefix= is deprecated and will be removed in a future version. ' \
116
+ 'Use `self.resource_prefix=` instead.'
117
+ )
118
+ self.resource_prefix = value
119
+ end
120
+
121
+ alias_method :set_prefix, :prefix=
122
+
123
+ def init_prefix(resource)
124
+ init_prefix_explicit(resource.to_s.pluralize, "#{resource}_id")
125
+ end
126
+
127
+ def init_prefix_explicit(resource_type, resource_id)
128
+ self.resource_prefix = "#{resource_type}/:#{resource_id}/"
129
+
130
+ define_method(resource_id.to_sym) do
131
+ @prefix_options[resource_id]
132
+ end
133
+ end
134
+
135
+ def early_july_pagination?
136
+ !!early_july_pagination
137
+ end
138
+
139
+ def version_validation!(minimum_version)
140
+ available_in_version = ShopifyAPI::ApiVersion.find_version(minimum_version)
141
+
142
+ unless ShopifyAPI::Base.api_version >= available_in_version
143
+ raise NotImplementedError, "The minimum supported version is #{minimum_version}."
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ attr_accessor :early_july_pagination
150
+
151
+ def early_july_pagination_release!
152
+ self.early_july_pagination = true
153
+ end
154
+ end
155
+
156
+ def persisted?
157
+ !id.nil?
158
+ end
159
+
160
+ private
161
+
162
+ def only_id
163
+ encode(only: :id, include: [], methods: [])
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyAPI
3
+ class BillingAddress < Base
4
+ end
5
+ end