disco_app 0.12.5

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 (354) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +37 -0
  3. data/app/assets/components/disco_app/buttons/model-destroy-button.es6.jsx +31 -0
  4. data/app/assets/components/disco_app/forms/model-form.es6.jsx +64 -0
  5. data/app/assets/components/embedded_app/bar.es6.jsx +31 -0
  6. data/app/assets/components/shopify/buttons/_buttons.scss +547 -0
  7. data/app/assets/components/shopify/buttons/button.es6.jsx +15 -0
  8. data/app/assets/components/shopify/card/_card.scss +342 -0
  9. data/app/assets/components/shopify/card/card-header.es6.jsx +34 -0
  10. data/app/assets/components/shopify/card/card-section.es6.jsx +26 -0
  11. data/app/assets/components/shopify/card/card.es6.jsx +16 -0
  12. data/app/assets/components/shopify/image/_image.scss +82 -0
  13. data/app/assets/components/shopify/table/_table.scss +18 -0
  14. data/app/assets/components/shopify/typography/_typography.scss +23 -0
  15. data/app/assets/components/shopify/typography/ui-heading.es6.jsx +16 -0
  16. data/app/assets/components/shopify/ui-layout/_ui-layout.scss +157 -0
  17. data/app/assets/components/shopify/ui-layout/ui-annotated-section.es6.jsx +29 -0
  18. data/app/assets/components/shopify/ui-layout/ui-empty-state.es6.jsx +35 -0
  19. data/app/assets/components/shopify/ui-layout/ui-footer-help.es6.jsx +13 -0
  20. data/app/assets/components/shopify/ui-layout/ui-layout-item.es6.jsx +11 -0
  21. data/app/assets/components/shopify/ui-layout/ui-layout-section.es6.jsx +19 -0
  22. data/app/assets/components/shopify/ui-layout/ui-layout-sections.es6.jsx +11 -0
  23. data/app/assets/components/shopify/ui-layout/ui-layout.es6.jsx +11 -0
  24. data/app/assets/components/shopify/ui-layout/ui-page-actions.es6.jsx +13 -0
  25. data/app/assets/components/shopify/ui-layout/ui-page-actions__buttons.es6.jsx +27 -0
  26. data/app/assets/components/shopify/ui-stack/_ui-stack.scss +39 -0
  27. data/app/assets/components/shopify/ui-stack/ui-stack-item.es6.jsx +21 -0
  28. data/app/assets/components/shopify/ui-stack/ui-stack.es6.jsx +24 -0
  29. data/app/assets/images/disco_app/icon.svg +1 -0
  30. data/app/assets/images/disco_app/icons.svg +0 -0
  31. data/app/assets/javascripts/disco_app/components/custom/filterable_shop_list.js.jsx +61 -0
  32. data/app/assets/javascripts/disco_app/components/custom/inline-radio-options.es6.jsx +59 -0
  33. data/app/assets/javascripts/disco_app/components/custom/rules-editor.es6.jsx +432 -0
  34. data/app/assets/javascripts/disco_app/components/custom/shop_filter_query.js.jsx +13 -0
  35. data/app/assets/javascripts/disco_app/components/custom/shop_filter_tab.js.jsx +34 -0
  36. data/app/assets/javascripts/disco_app/components/custom/shop_filter_tabs.js.jsx +21 -0
  37. data/app/assets/javascripts/disco_app/components/custom/shop_list.js.jsx +142 -0
  38. data/app/assets/javascripts/disco_app/components/custom/shop_row.js.jsx +43 -0
  39. data/app/assets/javascripts/disco_app/components/custom/shopify_admin_link.js.jsx +29 -0
  40. data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_form.es6.jsx +72 -0
  41. data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_input.es6.jsx +20 -0
  42. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-checkbox.es6.jsx +30 -0
  43. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-radio.es6.jsx +30 -0
  44. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-select.es6.jsx +45 -0
  45. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-text.es6.jsx +69 -0
  46. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-textarea.es6.jsx +48 -0
  47. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-time.es6.jsx +7 -0
  48. data/app/assets/javascripts/disco_app/components/ui-kit/forms/ui-form__element.es6.jsx +17 -0
  49. data/app/assets/javascripts/disco_app/components/ui-kit/forms/ui-form__group.es6.jsx +11 -0
  50. data/app/assets/javascripts/disco_app/components/ui-kit/forms/ui-form__section.es6.jsx +11 -0
  51. data/app/assets/javascripts/disco_app/components/ui-kit/icons/icon-chevron.es6.jsx +33 -0
  52. data/app/assets/javascripts/disco_app/components/ui-kit/icons/next-icon.es6.jsx +19 -0
  53. data/app/assets/javascripts/disco_app/components/ui-kit/input_select.es6.jsx +21 -0
  54. data/app/assets/javascripts/disco_app/components/ui-kit/tables/table.es6.jsx +27 -0
  55. data/app/assets/javascripts/disco_app/components.js +3 -0
  56. data/app/assets/javascripts/disco_app/disco_app.js +9 -0
  57. data/app/assets/javascripts/disco_app/frame.js +152 -0
  58. data/app/assets/javascripts/disco_app/shopify-turbolinks.js +7 -0
  59. data/app/assets/javascripts/disco_app/ui-kit.js +1 -0
  60. data/app/assets/stylesheets/disco_app/admin/_header.scss +75 -0
  61. data/app/assets/stylesheets/disco_app/admin/_layout.scss +32 -0
  62. data/app/assets/stylesheets/disco_app/admin/_nav.scss +184 -0
  63. data/app/assets/stylesheets/disco_app/admin.scss +11 -0
  64. data/app/assets/stylesheets/disco_app/disco_app.scss +26 -0
  65. data/app/assets/stylesheets/disco_app/frame/_buttons.scss +54 -0
  66. data/app/assets/stylesheets/disco_app/frame/_forms.scss +26 -0
  67. data/app/assets/stylesheets/disco_app/frame/_layout.scss +77 -0
  68. data/app/assets/stylesheets/disco_app/frame/_type.scss +25 -0
  69. data/app/assets/stylesheets/disco_app/frame.scss +10 -0
  70. data/app/assets/stylesheets/disco_app/mixins/_flexbox.scss +400 -0
  71. data/app/assets/stylesheets/disco_app/ui-kit/_ui-forms.scss +69 -0
  72. data/app/assets/stylesheets/disco_app/ui-kit/_ui-icons.scss +28 -0
  73. data/app/assets/stylesheets/disco_app/ui-kit/_ui-kit.scss +5121 -0
  74. data/app/assets/stylesheets/disco_app/ui-kit/_ui-layout.scss +15 -0
  75. data/app/assets/stylesheets/disco_app/ui-kit/_ui-tabs.scss +75 -0
  76. data/app/assets/stylesheets/disco_app/ui-kit/_ui-type.scss +13 -0
  77. data/app/clients/disco_app/api_client.rb +27 -0
  78. data/app/clients/disco_app/disco_api_error.rb +2 -0
  79. data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
  80. data/app/controllers/disco_app/admin/application_controller.rb +10 -0
  81. data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
  82. data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
  83. data/app/controllers/disco_app/admin/concerns/plans_controller.rb +54 -0
  84. data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
  85. data/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +29 -0
  86. data/app/controllers/disco_app/admin/plans_controller.rb +3 -0
  87. data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
  88. data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
  89. data/app/controllers/disco_app/admin/subscriptions_controller.rb +3 -0
  90. data/app/controllers/disco_app/charges_controller.rb +47 -0
  91. data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
  92. data/app/controllers/disco_app/concerns/authenticated_controller.rb +71 -0
  93. data/app/controllers/disco_app/concerns/carrier_request_controller.rb +35 -0
  94. data/app/controllers/disco_app/frame_controller.rb +9 -0
  95. data/app/controllers/disco_app/install_controller.rb +27 -0
  96. data/app/controllers/disco_app/subscriptions_controller.rb +32 -0
  97. data/app/controllers/disco_app/webhooks_controller.rb +46 -0
  98. data/app/controllers/sessions_controller.rb +33 -0
  99. data/app/helpers/disco_app/application_helper.rb +68 -0
  100. data/app/jobs/disco_app/app_installed_job.rb +3 -0
  101. data/app/jobs/disco_app/app_uninstalled_job.rb +3 -0
  102. data/app/jobs/disco_app/concerns/app_installed_job.rb +39 -0
  103. data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +21 -0
  104. data/app/jobs/disco_app/concerns/render_asset_group_job.rb +8 -0
  105. data/app/jobs/disco_app/concerns/shop_update_job.rb +13 -0
  106. data/app/jobs/disco_app/concerns/subscription_changed_job.rb +8 -0
  107. data/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +55 -0
  108. data/app/jobs/disco_app/concerns/synchronise_resources_job.rb +12 -0
  109. data/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +52 -0
  110. data/app/jobs/disco_app/render_asset_group_job.rb +3 -0
  111. data/app/jobs/disco_app/send_subscription_job.rb +7 -0
  112. data/app/jobs/disco_app/shop_job.rb +27 -0
  113. data/app/jobs/disco_app/shop_update_job.rb +3 -0
  114. data/app/jobs/disco_app/subscription_changed_job.rb +3 -0
  115. data/app/jobs/disco_app/synchronise_carrier_service_job.rb +3 -0
  116. data/app/jobs/disco_app/synchronise_resources_job.rb +3 -0
  117. data/app/jobs/disco_app/synchronise_webhooks_job.rb +3 -0
  118. data/app/models/disco_app/app_settings.rb +3 -0
  119. data/app/models/disco_app/application_charge.rb +18 -0
  120. data/app/models/disco_app/concerns/app_settings.rb +7 -0
  121. data/app/models/disco_app/concerns/can_be_liquified.rb +45 -0
  122. data/app/models/disco_app/concerns/has_metafields.rb +48 -0
  123. data/app/models/disco_app/concerns/plan.rb +26 -0
  124. data/app/models/disco_app/concerns/plan_code.rb +15 -0
  125. data/app/models/disco_app/concerns/renders_assets.rb +166 -0
  126. data/app/models/disco_app/concerns/shop.rb +96 -0
  127. data/app/models/disco_app/concerns/subscription.rb +66 -0
  128. data/app/models/disco_app/concerns/synchronises.rb +58 -0
  129. data/app/models/disco_app/concerns/taggable.rb +16 -0
  130. data/app/models/disco_app/plan.rb +3 -0
  131. data/app/models/disco_app/plan_code.rb +3 -0
  132. data/app/models/disco_app/recurring_application_charge.rb +18 -0
  133. data/app/models/disco_app/session_storage.rb +18 -0
  134. data/app/models/disco_app/shop.rb +3 -0
  135. data/app/models/disco_app/subscription.rb +3 -0
  136. data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +100 -0
  137. data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
  138. data/app/services/disco_app/carrier_request_service.rb +15 -0
  139. data/app/services/disco_app/charges_service.rb +81 -0
  140. data/app/services/disco_app/proxy_service.rb +17 -0
  141. data/app/services/disco_app/request_validation_service.rb +15 -0
  142. data/app/services/disco_app/subscription_service.rb +46 -0
  143. data/app/services/disco_app/webhook_service.rb +30 -0
  144. data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
  145. data/app/views/disco_app/admin/plans/_form.html.erb +72 -0
  146. data/app/views/disco_app/admin/plans/_plan_code_fields.html.erb +15 -0
  147. data/app/views/disco_app/admin/plans/edit.html.erb +7 -0
  148. data/app/views/disco_app/admin/plans/index.html.erb +43 -0
  149. data/app/views/disco_app/admin/plans/new.html.erb +7 -0
  150. data/app/views/disco_app/admin/shops/index.html.erb +13 -0
  151. data/app/views/disco_app/admin/subscriptions/edit.html.erb +33 -0
  152. data/app/views/disco_app/charges/activate.html.erb +1 -0
  153. data/app/views/disco_app/charges/create.html.erb +1 -0
  154. data/app/views/disco_app/charges/new.html.erb +23 -0
  155. data/app/views/disco_app/frame/frame.html.erb +36 -0
  156. data/app/views/disco_app/install/installing.html.erb +22 -0
  157. data/app/views/disco_app/install/uninstalling.html.erb +1 -0
  158. data/app/views/disco_app/proxy_errors/404.html.erb +1 -0
  159. data/app/views/disco_app/shared/_card.html.erb +14 -0
  160. data/app/views/disco_app/shared/_icons.html.erb +1 -0
  161. data/app/views/disco_app/shared/_section.html.erb +17 -0
  162. data/app/views/disco_app/subscriptions/new.html.erb +25 -0
  163. data/app/views/layouts/admin/_nav_items.erb +20 -0
  164. data/app/views/layouts/admin.html.erb +67 -0
  165. data/app/views/layouts/application.html.erb +18 -0
  166. data/app/views/layouts/embedded_app.html.erb +50 -0
  167. data/app/views/layouts/embedded_app_modal.html.erb +28 -0
  168. data/app/views/shopify_app/sessions/new.html.erb +42 -0
  169. data/config/routes.rb +64 -0
  170. data/db/migrate/20150525000000_create_shops_if_not_existent.rb +110 -0
  171. data/lib/disco_app/configuration.rb +45 -0
  172. data/lib/disco_app/constants.rb +4 -0
  173. data/lib/disco_app/engine.rb +26 -0
  174. data/lib/disco_app/session.rb +14 -0
  175. data/lib/disco_app/support/file_fixtures.rb +23 -0
  176. data/lib/disco_app/test_help.rb +11 -0
  177. data/lib/disco_app/version.rb +3 -0
  178. data/lib/disco_app.rb +7 -0
  179. data/lib/generators/disco_app/USAGE +5 -0
  180. data/lib/generators/disco_app/disco_app_generator.rb +236 -0
  181. data/lib/generators/disco_app/templates/assets/javascripts/application.js +17 -0
  182. data/lib/generators/disco_app/templates/assets/javascripts/components.js +3 -0
  183. data/lib/generators/disco_app/templates/assets/stylesheets/application.scss +5 -0
  184. data/lib/generators/disco_app/templates/config/database.yml.tt +20 -0
  185. data/lib/generators/disco_app/templates/config/newrelic.yml +26 -0
  186. data/lib/generators/disco_app/templates/config/puma.rb +15 -0
  187. data/lib/generators/disco_app/templates/controllers/home_controller.rb +7 -0
  188. data/lib/generators/disco_app/templates/initializers/disco_app.rb +28 -0
  189. data/lib/generators/disco_app/templates/initializers/rollbar.rb +23 -0
  190. data/lib/generators/disco_app/templates/initializers/session_store.rb +2 -0
  191. data/lib/generators/disco_app/templates/initializers/shopify_app.rb +6 -0
  192. data/lib/generators/disco_app/templates/initializers/shopify_session_repository.rb +7 -0
  193. data/lib/generators/disco_app/templates/root/CHECKS +4 -0
  194. data/lib/generators/disco_app/templates/root/Procfile +2 -0
  195. data/lib/generators/disco_app/templates/views/home/index.html.erb +2 -0
  196. data/lib/tasks/api.rake +10 -0
  197. data/lib/tasks/carrier_service.rake +10 -0
  198. data/lib/tasks/database.rake +8 -0
  199. data/lib/tasks/sessions.rake +9 -0
  200. data/lib/tasks/shops.rake +10 -0
  201. data/lib/tasks/start.rake +3 -0
  202. data/lib/tasks/webhooks.rake +10 -0
  203. data/test/clients/disco_app/api_client_test.rb +22 -0
  204. data/test/controllers/disco_app/admin/shops_controller_test.rb +54 -0
  205. data/test/controllers/disco_app/charges_controller_test.rb +99 -0
  206. data/test/controllers/disco_app/install_controller_test.rb +50 -0
  207. data/test/controllers/disco_app/subscriptions_controller_test.rb +68 -0
  208. data/test/controllers/disco_app/webhooks_controller_test.rb +58 -0
  209. data/test/controllers/home_controller_test.rb +101 -0
  210. data/test/controllers/proxy_controller_test.rb +42 -0
  211. data/test/disco_app_test.rb +7 -0
  212. data/test/dummy/Rakefile +6 -0
  213. data/test/dummy/app/assets/javascripts/application.js +17 -0
  214. data/test/dummy/app/assets/stylesheets/application.scss +5 -0
  215. data/test/dummy/app/controllers/application_controller.rb +6 -0
  216. data/test/dummy/app/controllers/carrier_request_controller.rb +10 -0
  217. data/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +8 -0
  218. data/test/dummy/app/controllers/home_controller.rb +7 -0
  219. data/test/dummy/app/controllers/proxy_controller.rb +8 -0
  220. data/test/dummy/app/helpers/application_helper.rb +2 -0
  221. data/test/dummy/app/jobs/carts_update_job.rb +7 -0
  222. data/test/dummy/app/jobs/disco_app/app_installed_job.rb +16 -0
  223. data/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +11 -0
  224. data/test/dummy/app/jobs/products_create_job.rb +7 -0
  225. data/test/dummy/app/jobs/products_delete_job.rb +7 -0
  226. data/test/dummy/app/jobs/products_update_job.rb +7 -0
  227. data/test/dummy/app/models/cart.rb +24 -0
  228. data/test/dummy/app/models/disco_app/shop.rb +20 -0
  229. data/test/dummy/app/models/js_configuration.rb +8 -0
  230. data/test/dummy/app/models/product.rb +9 -0
  231. data/test/dummy/app/models/widget_configuration.rb +10 -0
  232. data/test/dummy/app/views/assets/script_tag.js.erb +1 -0
  233. data/test/dummy/app/views/assets/test.js.erb +1 -0
  234. data/test/dummy/app/views/assets/widget.js.erb +2 -0
  235. data/test/dummy/app/views/assets/widget.scss.erb +3 -0
  236. data/test/dummy/app/views/home/index.html.erb +2 -0
  237. data/test/dummy/app/views/snippets/widget.liquid.erb +1 -0
  238. data/test/dummy/bin/bundle +3 -0
  239. data/test/dummy/bin/rails +4 -0
  240. data/test/dummy/bin/rake +4 -0
  241. data/test/dummy/bin/setup +29 -0
  242. data/test/dummy/config/application.rb +38 -0
  243. data/test/dummy/config/boot.rb +5 -0
  244. data/test/dummy/config/database.codeship.yml +23 -0
  245. data/test/dummy/config/database.gitlab-ci.yml +24 -0
  246. data/test/dummy/config/database.yml +20 -0
  247. data/test/dummy/config/environment.rb +5 -0
  248. data/test/dummy/config/environments/development.rb +41 -0
  249. data/test/dummy/config/environments/production.rb +85 -0
  250. data/test/dummy/config/environments/test.rb +42 -0
  251. data/test/dummy/config/initializers/assets.rb +11 -0
  252. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  253. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  254. data/test/dummy/config/initializers/disco_app.rb +28 -0
  255. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  256. data/test/dummy/config/initializers/inflections.rb +16 -0
  257. data/test/dummy/config/initializers/mime_types.rb +4 -0
  258. data/test/dummy/config/initializers/omniauth.rb +7 -0
  259. data/test/dummy/config/initializers/session_store.rb +2 -0
  260. data/test/dummy/config/initializers/shopify_app.rb +6 -0
  261. data/test/dummy/config/initializers/shopify_session_repository.rb +7 -0
  262. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  263. data/test/dummy/config/locales/en.yml +23 -0
  264. data/test/dummy/config/routes.rb +11 -0
  265. data/test/dummy/config/secrets.yml +22 -0
  266. data/test/dummy/config.ru +4 -0
  267. data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
  268. data/test/dummy/db/migrate/20160530160739_create_asset_models.rb +19 -0
  269. data/test/dummy/db/migrate/20161105054746_create_carts.rb +13 -0
  270. data/test/dummy/db/schema.rb +152 -0
  271. data/test/dummy/public/404.html +67 -0
  272. data/test/dummy/public/422.html +67 -0
  273. data/test/dummy/public/500.html +66 -0
  274. data/test/dummy/public/favicon.ico +0 -0
  275. data/test/fixtures/api/subscriptions/valid_request.json +40 -0
  276. data/test/fixtures/api/widget_store/assets/create_script_tag_js_request.json +6 -0
  277. data/test/fixtures/api/widget_store/assets/create_script_tag_js_response.json +12 -0
  278. data/test/fixtures/api/widget_store/assets/create_script_tag_request.json +6 -0
  279. data/test/fixtures/api/widget_store/assets/create_script_tag_response.json +10 -0
  280. data/test/fixtures/api/widget_store/assets/create_test_js_request.json +6 -0
  281. data/test/fixtures/api/widget_store/assets/create_test_js_response.json +12 -0
  282. data/test/fixtures/api/widget_store/assets/create_widget_js_request.json +6 -0
  283. data/test/fixtures/api/widget_store/assets/create_widget_js_response.json +12 -0
  284. data/test/fixtures/api/widget_store/assets/create_widget_liquid_request.json +6 -0
  285. data/test/fixtures/api/widget_store/assets/create_widget_liquid_response.json +12 -0
  286. data/test/fixtures/api/widget_store/assets/create_widget_scss_request.json +6 -0
  287. data/test/fixtures/api/widget_store/assets/create_widget_scss_response.json +12 -0
  288. data/test/fixtures/api/widget_store/assets/get_script_tags_empty_request.json +1 -0
  289. data/test/fixtures/api/widget_store/assets/get_script_tags_empty_response.json +1 -0
  290. data/test/fixtures/api/widget_store/assets/get_script_tags_preexisting_request.json +1 -0
  291. data/test/fixtures/api/widget_store/assets/get_script_tags_preexisting_response.json +12 -0
  292. data/test/fixtures/api/widget_store/assets/update_script_tag_request.json +10 -0
  293. data/test/fixtures/api/widget_store/assets/update_script_tag_response.json +10 -0
  294. data/test/fixtures/api/widget_store/carrier_services.json +1 -0
  295. data/test/fixtures/api/widget_store/carrier_services_create.json +8 -0
  296. data/test/fixtures/api/widget_store/charges/activate_application_charge_request.json +16 -0
  297. data/test/fixtures/api/widget_store/charges/activate_application_charge_response.json +1 -0
  298. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_request.json +20 -0
  299. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_response.json +1 -0
  300. data/test/fixtures/api/widget_store/charges/create_application_charge_request.json +9 -0
  301. data/test/fixtures/api/widget_store/charges/create_application_charge_response.json +16 -0
  302. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +9 -0
  303. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_response.json +20 -0
  304. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +9 -0
  305. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_response.json +20 -0
  306. data/test/fixtures/api/widget_store/charges/get_accepted_application_charge_response.json +16 -0
  307. data/test/fixtures/api/widget_store/charges/get_accepted_recurring_application_charge_response.json +20 -0
  308. data/test/fixtures/api/widget_store/charges/get_declined_application_charge_response.json +16 -0
  309. data/test/fixtures/api/widget_store/charges/get_declined_recurring_application_charge_response.json +20 -0
  310. data/test/fixtures/api/widget_store/charges/get_pending_application_charge_response.json +16 -0
  311. data/test/fixtures/api/widget_store/charges/get_pending_recurring_application_charge_response.json +20 -0
  312. data/test/fixtures/api/widget_store/products/write_metafields_multiple_namespaces_request.json +31 -0
  313. data/test/fixtures/api/widget_store/products/write_metafields_multiple_namespaces_response.json +1 -0
  314. data/test/fixtures/api/widget_store/products/write_metafields_single_namespace_request.json +19 -0
  315. data/test/fixtures/api/widget_store/products/write_metafields_single_namespace_response.json +1 -0
  316. data/test/fixtures/api/widget_store/shop.json +46 -0
  317. data/test/fixtures/api/widget_store/webhooks.json +1 -0
  318. data/test/fixtures/assets/test.js +1 -0
  319. data/test/fixtures/assets/test.min.js +1 -0
  320. data/test/fixtures/carts.yml +5 -0
  321. data/test/fixtures/disco_app/application_charges.yml +11 -0
  322. data/test/fixtures/disco_app/plan_codes.yml +13 -0
  323. data/test/fixtures/disco_app/plans.yml +37 -0
  324. data/test/fixtures/disco_app/recurring_application_charges.yml +13 -0
  325. data/test/fixtures/disco_app/shops.yml +12 -0
  326. data/test/fixtures/disco_app/subscriptions.yml +25 -0
  327. data/test/fixtures/js_configurations.yml +3 -0
  328. data/test/fixtures/liquid/model.liquid +8 -0
  329. data/test/fixtures/products.yml +4 -0
  330. data/test/fixtures/webhooks/app_uninstalled.json +46 -0
  331. data/test/fixtures/webhooks/cart_updated.json +28 -0
  332. data/test/fixtures/webhooks/product_created.json +167 -0
  333. data/test/fixtures/webhooks/product_deleted.json +3 -0
  334. data/test/fixtures/webhooks/product_updated.json +167 -0
  335. data/test/fixtures/widget_configurations.yml +4 -0
  336. data/test/integration/synchronises_test.rb +62 -0
  337. data/test/jobs/disco_app/app_installed_job_test.rb +57 -0
  338. data/test/jobs/disco_app/app_uninstalled_job_test.rb +30 -0
  339. data/test/jobs/disco_app/send_subscription_job_test.rb +24 -0
  340. data/test/jobs/disco_app/synchronise_carrier_service_job_test.rb +25 -0
  341. data/test/jobs/disco_app/synchronise_webhooks_job_test.rb +30 -0
  342. data/test/models/disco_app/can_be_liquified_test.rb +55 -0
  343. data/test/models/disco_app/has_metafields_test.rb +40 -0
  344. data/test/models/disco_app/plan_test.rb +5 -0
  345. data/test/models/disco_app/renders_assets_test.rb +109 -0
  346. data/test/models/disco_app/session_test.rb +31 -0
  347. data/test/models/disco_app/shop_test.rb +43 -0
  348. data/test/models/disco_app/subscription_test.rb +19 -0
  349. data/test/services/disco_app/charges_service_test.rb +112 -0
  350. data/test/services/disco_app/subscription_service_test.rb +60 -0
  351. data/test/support/test_file_fixtures.rb +29 -0
  352. data/test/support/test_shopify_api.rb +16 -0
  353. data/test/test_helper.rb +58 -0
  354. metadata +987 -0
@@ -0,0 +1,68 @@
1
+ module DiscoApp::ApplicationHelper
2
+
3
+ # Generates a link pointing to an object (such as an order or customer) inside
4
+ # the given shop's Shopify admin. This helper makes it easy to create links
5
+ # to objects within the admin that support both right-clicking and opening in
6
+ # a new tab as well as capturing a left click and redirecting to the relevant
7
+ # object using `ShopifyApp.redirect()`.
8
+ def link_to_shopify_admin(shop, name, admin_path, options = {})
9
+ options[:onclick] = "ShopifyApp.redirect('#{admin_path}'); return false;"
10
+ options[:'data-no-turbolink'] = true
11
+ link_to(name, "https://#{shop.shopify_domain}/admin/#{admin_path}", options)
12
+ end
13
+
14
+ # Generate a link that will open its href in an embedded Shopify modal.
15
+ def link_to_modal(name, path, options = {})
16
+ modal_options = {
17
+ src: path,
18
+ title: options.delete(:modal_title),
19
+ width: options.delete(:modal_width),
20
+ height: options.delete(:modal_height),
21
+ buttons: options.delete(:modal_buttons),
22
+ }
23
+ options[:onclick] = "ShopifyApp.Modal.open(#{modal_options.to_json}); return false;"
24
+ options[:onclick].gsub!(/"function(.*?)"/, 'function\1')
25
+ link_to(name, path, options)
26
+ end
27
+
28
+ # Render a React component with inner HTML content.
29
+ # Thanks to https://github.com/reactjs/react-rails/pull/166#issuecomment-86178980
30
+ def react_component_with_content(name, args = {}, options = {}, &block)
31
+ args[:__html] = capture(&block) if block.present?
32
+ react_component(name, args, options)
33
+ end
34
+
35
+ # Provide link to dynamically add a new nested fields association
36
+ def link_to_add_fields(name, f, association)
37
+ new_object = f.object.send(association).klass.new
38
+ id = new_object.object_id
39
+ fields = f.fields_for(association, new_object, child_index: id) do |builder|
40
+ render(association.to_s.singularize + "_fields", f: builder)
41
+ end
42
+ link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
43
+ end
44
+
45
+ # Return the props required to instantiate a React ModelForm component for the
46
+ # given model instance.
47
+ def model_form_props(model)
48
+ {
49
+ model: model,
50
+ modelTitle: model.persisted? ? model.to_s : "New #{model.model_name.human.downcase}",
51
+ modelName: model.model_name.singular,
52
+ modelUrl: model.persisted? ? send("#{model.model_name.singular}_path", model) : nil,
53
+ modelsUrl: send("#{model.model_name.plural}_path"),
54
+ authenticityToken: form_authenticity_token,
55
+ errors: errors_to_react(model)
56
+ }.as_json
57
+ end
58
+
59
+ # Helper method that provides detailed error information from an active record as JSON
60
+ def errors_to_react(model)
61
+ {
62
+ type: model.model_name.human.downcase,
63
+ errors: model.errors.keys,
64
+ messages: model.errors.full_messages
65
+ }.as_json
66
+ end
67
+
68
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppInstalledJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::AppInstalledJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppUninstalledJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::AppUninstalledJob
3
+ end
@@ -0,0 +1,39 @@
1
+ module DiscoApp::Concerns::AppInstalledJob
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_enqueue { @shop.awaiting_install! }
6
+ before_perform { @shop.installing! }
7
+ after_perform { @shop.installed! }
8
+ end
9
+
10
+ # Perform application installation.
11
+ #
12
+ # - Synchronise webhooks.
13
+ # - Synchronise carrier service, if required.
14
+ # - Perform initial update of shop information.
15
+ # - Subscribe to default plan, if any exists.
16
+ #
17
+ def perform(shop, plan_code = nil, source = nil)
18
+ DiscoApp::SynchroniseWebhooksJob.perform_now(@shop)
19
+ DiscoApp::SynchroniseCarrierServiceJob.perform_now(@shop)
20
+ DiscoApp::ShopUpdateJob.perform_now(@shop)
21
+
22
+ @shop.reload
23
+
24
+ if default_plan.present?
25
+ DiscoApp::SubscriptionService.subscribe(@shop, default_plan, plan_code, source)
26
+ end
27
+ end
28
+
29
+ # Provide an overridable hook for applications to examine the @shop object
30
+ # and return the default plan, if any, the shop should be subscribed to. If
31
+ # nil is returned, no automatic subscription will take place and the store
32
+ # owner will be forced to choose a plan after installation.
33
+ #
34
+ # If implementing this method, it should be memoized.
35
+ def default_plan
36
+ nil
37
+ end
38
+
39
+ end
@@ -0,0 +1,21 @@
1
+ module DiscoApp::Concerns::AppUninstalledJob
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_enqueue { @shop.awaiting_uninstall! }
6
+ before_perform { @shop.uninstalling! }
7
+ after_perform { @shop.uninstalled! }
8
+ end
9
+
10
+ # Perform application uninstallation.
11
+ #
12
+ # - Mark any recurring application charges as cancelled.
13
+ # - Remove any stored sessions for the shop.
14
+ #
15
+ def perform(shop, shop_data)
16
+ DiscoApp::ChargesService.cancel_recurring_charges(@shop)
17
+ DiscoApp::SendSubscriptionJob.perform_later(@shop)
18
+ @shop.sessions.delete_all
19
+ end
20
+
21
+ end
@@ -0,0 +1,8 @@
1
+ module DiscoApp::Concerns::RenderAssetGroupJob
2
+ extend ActiveSupport::Concern
3
+
4
+ def perform(shop, instance, asset_group)
5
+ instance.render_asset_group(asset_group.to_sym)
6
+ end
7
+
8
+ end
@@ -0,0 +1,13 @@
1
+ module DiscoApp::Concerns::ShopUpdateJob
2
+ extend ActiveSupport::Concern
3
+
4
+ # Perform an update of the current shop's information.
5
+ def perform(shop, shop_data = nil)
6
+ # If we weren't provided with shop data (eg from a webhook), fetch it.
7
+ shop_data ||= ActiveSupport::JSON::decode(ShopifyAPI::Shop.current.to_json)
8
+
9
+ # Update attributes stored directly on the Shop model, along with the data hash itself.
10
+ @shop.update(shop_data.with_indifferent_access.slice(*DiscoApp::Shop.column_names).except(:id, :created_at).merge(data: shop_data))
11
+ end
12
+
13
+ end
@@ -0,0 +1,8 @@
1
+ module DiscoApp::Concerns::SubscriptionChangedJob
2
+ extend ActiveSupport::Concern
3
+
4
+ def perform(shop, subscription)
5
+ DiscoApp::SendSubscriptionJob.perform_later(@shop)
6
+ end
7
+
8
+ end
@@ -0,0 +1,55 @@
1
+ module DiscoApp::Concerns::SynchroniseCarrierServiceJob
2
+ extend ActiveSupport::Concern
3
+
4
+ # Ensure that any carrier service required by our app is registered.
5
+ def perform(shop)
6
+ # Don't proceed unless we have a name and callback url.
7
+ return unless carrier_service_name and callback_url
8
+
9
+ # Registered the carrier service if it hasn't been registered yet.
10
+ unless current_carrier_service_names.include?(carrier_service_name)
11
+ ShopifyAPI::CarrierService.create(
12
+ name: carrier_service_name,
13
+ callback_url: callback_url,
14
+ service_discovery: true,
15
+ format: :json
16
+ )
17
+ end
18
+
19
+ # Ensure any existing carrier services (with the correct name) are active
20
+ # and have a current callback URL.
21
+ current_carrier_services.each do |carrier_service|
22
+ if carrier_service.name == carrier_service_name
23
+ carrier_service.callback_url = callback_url
24
+ carrier_service.active = true
25
+ carrier_service.save
26
+ end
27
+ end
28
+ end
29
+
30
+ protected
31
+
32
+ def carrier_service_name
33
+ DiscoApp.configuration.app_name
34
+ end
35
+
36
+ def callback_url
37
+ @callback_url ||= begin
38
+ callback_url = DiscoApp.configuration.carrier_service_callback_url
39
+ callback_url.respond_to?('call') ? callback_url.call : callback_url
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Return a list of currently registered carrier service names.
46
+ def current_carrier_service_names
47
+ current_carrier_services.map(&:name)
48
+ end
49
+
50
+ # Return a list of currently registered carrier services.
51
+ def current_carrier_services
52
+ @current_carrier_service ||= ShopifyAPI::CarrierService.find(:all)
53
+ end
54
+
55
+ end
@@ -0,0 +1,12 @@
1
+ module DiscoApp::Concerns::SynchroniseResourcesJob
2
+ extend ActiveSupport::Concern
3
+
4
+ def perform(shop, class_name, params)
5
+ klass = class_name.constantize
6
+
7
+ klass::SHOPIFY_API_CLASS.find(:all, params: params).map do |shopify_resource|
8
+ klass.synchronise(@shop, shopify_resource.serializable_hash)
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,52 @@
1
+ module DiscoApp::Concerns::SynchroniseWebhooksJob
2
+ extend ActiveSupport::Concern
3
+
4
+ # Ensure the webhooks registered with our shop are the same as those listed
5
+ # in our application configuration.
6
+ def perform(shop)
7
+ # Get the full list of expected webhook topics.
8
+ expected_topics = [:'app/uninstalled', :'shop/update'] + (DiscoApp.configuration.webhook_topics || [])
9
+
10
+ # Registered any webhooks that haven't been registered yet.
11
+ (expected_topics - current_topics).each do |topic|
12
+ ShopifyAPI::Webhook.create(
13
+ topic: topic,
14
+ address: webhooks_url,
15
+ format: 'json'
16
+ )
17
+ end
18
+
19
+ # Remove any extraneous topics.
20
+ current_webhooks.each do |webhook|
21
+ unless expected_topics.include?(webhook.topic.to_sym)
22
+ ShopifyAPI::Webhook.delete(webhook.id)
23
+ end
24
+ end
25
+
26
+ # Ensure webhook addresses are current.
27
+ current_webhooks.each do |webhook|
28
+ unless webhook.address == webhooks_url
29
+ webhook.address = webhooks_url
30
+ webhook.save
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Return a list of currently registered topics.
38
+ def current_topics
39
+ current_webhooks.map(&:topic).map(&:to_sym)
40
+ end
41
+
42
+ # Return a list of current registered webhooks.
43
+ def current_webhooks
44
+ @current_webhooks ||= ShopifyAPI::Webhook.find(:all)
45
+ end
46
+
47
+ # Return the absolute URL to the webhooks endpoint.
48
+ def webhooks_url
49
+ DiscoApp::Engine.routes.url_helpers.webhooks_url
50
+ end
51
+
52
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::RenderAssetGroupJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::RenderAssetGroupJob
3
+ end
@@ -0,0 +1,7 @@
1
+ class DiscoApp::SendSubscriptionJob < DiscoApp::ShopJob
2
+
3
+ def perform(shop)
4
+ @shop.disco_api_client.create_app_subscription
5
+ end
6
+
7
+ end
@@ -0,0 +1,27 @@
1
+ # The base class for all jobs that should be performed in the context of a
2
+ # particular Shop's API session. The first argument to any job inheriting from
3
+ # this class must be the domain of the relevant store, so that the appropriate
4
+ # Shop model can be fetched and the temporary API session created.
5
+ class DiscoApp::ShopJob < ActiveJob::Base
6
+
7
+ queue_as :default
8
+
9
+ before_perform { |job| find_shop(job) }
10
+ before_enqueue { |job| find_shop(job) }
11
+
12
+ around_enqueue { |job, block| shop_context(job, block) }
13
+ around_perform { |job, block| shop_context(job, block) }
14
+
15
+ private
16
+
17
+ def find_shop(job)
18
+ @shop ||= job.arguments.first.is_a?(DiscoApp::Shop) ? job.arguments.first : DiscoApp::Shop.find_by!(shopify_domain: job.arguments.first)
19
+ end
20
+
21
+ def shop_context(job, block)
22
+ @shop.with_api_context {
23
+ block.call(job.arguments)
24
+ }
25
+ end
26
+
27
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::ShopUpdateJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::ShopUpdateJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::SubscriptionChangedJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::SubscriptionChangedJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::SynchroniseCarrierServiceJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::SynchroniseCarrierServiceJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::SynchroniseResourcesJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::SynchroniseResourcesJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::SynchroniseWebhooksJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::SynchroniseWebhooksJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppSettings < ActiveRecord::Base
2
+ include DiscoApp::Concerns::AppSettings
3
+ end
@@ -0,0 +1,18 @@
1
+ class DiscoApp::ApplicationCharge < ActiveRecord::Base
2
+
3
+ belongs_to :shop
4
+ belongs_to :subscription
5
+
6
+ enum status: [:pending, :accepted, :declined, :expired, :active]
7
+
8
+ scope :active, -> { where status: statuses[:active] }
9
+
10
+ def recurring?
11
+ false
12
+ end
13
+
14
+ def activate_url
15
+ DiscoApp::Engine.routes.url_helpers.activate_subscription_charge_url(subscription, self)
16
+ end
17
+
18
+ end
@@ -0,0 +1,7 @@
1
+ module DiscoApp::Concerns::AppSettings
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ acts_as_singleton
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ module DiscoApp::Concerns::CanBeLiquified
2
+ extend ActiveSupport::Concern
3
+
4
+ SPLIT_ARRAY_SEPARATOR = '@!@'
5
+
6
+ included do
7
+
8
+ # Return this model as an array of Liquid {% assign %} statements.
9
+ def as_liquid
10
+ as_json.map { |k, v| "{% assign #{liquid_model_name}_#{k} = #{as_liquid_value(k, v)} %}" }
11
+ end
12
+
13
+ # Render this model as a series of concatenated Liquid {% assign %} statements.
14
+ def to_liquid
15
+ as_liquid.join("\n")
16
+ end
17
+
18
+ # Method to allow override of the model name in Liquid. Useful for models
19
+ # residing in namespaces that would otherwise have very long prefixes.
20
+ def liquid_model_name
21
+ model_name.param_key
22
+ end
23
+
24
+ private
25
+
26
+ # Return given value as a string expression that will be evaluated in Liquid to result in the correct value type.
27
+ def as_liquid_value(key, value)
28
+ if value.is_a? Numeric or (!!value == value)
29
+ return value.to_s
30
+ end
31
+ if value.nil?
32
+ return 'nil'
33
+ end
34
+ if value.is_a? Array
35
+ return "'#{value.map { |e| (e.is_a? String) ? CGI::escapeHTML(e) : e }.join(SPLIT_ARRAY_SEPARATOR)}' | split: '#{SPLIT_ARRAY_SEPARATOR}'"
36
+ end
37
+ if value.is_a? String and key.end_with? '_html'
38
+ return "'#{value.to_s.gsub("'", "&#39;")}'"
39
+ end
40
+ "'#{CGI::escapeHTML(value.to_s)}'"
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,48 @@
1
+ module DiscoApp::Concerns::HasMetafields
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ # Write multiple metafields for this object in a single call.
7
+ #
8
+ # Expects an argument in a nested hash structure with :namespace => :key =>
9
+ # :value, eg:
10
+ #
11
+ # Product.write_metafields(myapp: {
12
+ # key1: 'value1',
13
+ # key2: 'value2'
14
+ # })
15
+ #
16
+ # This method assumes that it is being called within a valid Shopify API
17
+ # session context, eg @shop.with_api_context { ... }.
18
+ #
19
+ # It also assumes that the including class has defined the appropriate value
20
+ # for SHOPIFY_API_CLASS and that calling the `id` method on the instance
21
+ # will return the relevant object's Shopify ID.
22
+ #
23
+ # Returns true on success, false otherwise.
24
+ def write_metafields(metafields)
25
+ self.class::SHOPIFY_API_CLASS.new(
26
+ id: id,
27
+ metafields: build_metafields(metafields)
28
+ ).save
29
+ end
30
+
31
+ # Give a nested hash of metafields in the format described above, return
32
+ # an array of corresponding ShopifyAPI::Metafield instances.
33
+ def build_metafields(metafields)
34
+ metafields.map do |namespace, keys|
35
+ keys.map do |key, value|
36
+ ShopifyAPI::Metafield.new(
37
+ namespace: namespace,
38
+ key: key,
39
+ value: value,
40
+ value_type: value.is_a?(Integer) ? :integer : :string
41
+ )
42
+ end
43
+ end.flatten
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,26 @@
1
+ module DiscoApp::Concerns::Plan
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ has_many :subscriptions
7
+ has_many :shops, through: :subscriptions
8
+ has_many :plan_codes, dependent: :destroy
9
+
10
+ accepts_nested_attributes_for :plan_codes, allow_destroy: true
11
+
12
+ enum status: [:available, :unavailable]
13
+ enum plan_type: [:recurring, :one_time]
14
+ enum interval: [:month, :year]
15
+
16
+ scope :available, -> { where status: statuses[:available] }
17
+
18
+ validates_presence_of :name
19
+
20
+ end
21
+
22
+ def has_trial?
23
+ trial_period_days.present? and trial_period_days > 0
24
+ end
25
+
26
+ end
@@ -0,0 +1,15 @@
1
+ module DiscoApp::Concerns::PlanCode
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ belongs_to :plan
7
+
8
+ enum status: [:available, :unavailable]
9
+
10
+ validates_presence_of :code
11
+ validates_presence_of :amount
12
+
13
+ end
14
+
15
+ end