disco_app 0.9.8

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 (335) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +37 -0
  3. data/app/assets/images/disco_app/icon.svg +1 -0
  4. data/app/assets/images/disco_app/icons.svg +0 -0
  5. data/app/assets/javascripts/disco_app/components/custom/filterable_shop_list.js.jsx +61 -0
  6. data/app/assets/javascripts/disco_app/components/custom/inline-radio-options.es6.jsx +59 -0
  7. data/app/assets/javascripts/disco_app/components/custom/rules-editor.es6.jsx +417 -0
  8. data/app/assets/javascripts/disco_app/components/custom/shop_filter_query.js.jsx +13 -0
  9. data/app/assets/javascripts/disco_app/components/custom/shop_filter_tab.js.jsx +34 -0
  10. data/app/assets/javascripts/disco_app/components/custom/shop_filter_tabs.js.jsx +21 -0
  11. data/app/assets/javascripts/disco_app/components/custom/shop_list.js.jsx +142 -0
  12. data/app/assets/javascripts/disco_app/components/custom/shop_row.js.jsx +43 -0
  13. data/app/assets/javascripts/disco_app/components/custom/shopify_admin_link.js.jsx +29 -0
  14. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-footer.es6.jsx +11 -0
  15. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-header.es6.jsx +11 -0
  16. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-section.es6.jsx +30 -0
  17. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card.es6.jsx +16 -0
  18. data/app/assets/javascripts/disco_app/components/ui-kit/cards/cart-section-title.es6.jsx +9 -0
  19. data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_form.es6.jsx +72 -0
  20. data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_input.es6.jsx +20 -0
  21. data/app/assets/javascripts/disco_app/components/ui-kit/forms/button.es6.jsx +13 -0
  22. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-radio.es6.jsx +30 -0
  23. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-select.es6.jsx +39 -0
  24. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-text.es6.jsx +59 -0
  25. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-textarea.es6.jsx +48 -0
  26. data/app/assets/javascripts/disco_app/components/ui-kit/icons/icon-chevron.es6.jsx +33 -0
  27. data/app/assets/javascripts/disco_app/components/ui-kit/icons/next-icon.es6.jsx +18 -0
  28. data/app/assets/javascripts/disco_app/components/ui-kit/input_select.es6.jsx +21 -0
  29. data/app/assets/javascripts/disco_app/components/ui-kit/tables/table.es6.jsx +22 -0
  30. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-annotated-section.es6.jsx +29 -0
  31. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-empty-state.es6.jsx +35 -0
  32. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-footer-help.es6.jsx +13 -0
  33. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout-item.es6.jsx +11 -0
  34. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout-section.es6.jsx +19 -0
  35. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout-sections.es6.jsx +11 -0
  36. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout.es6.jsx +11 -0
  37. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-page-actions.es6.jsx +48 -0
  38. data/app/assets/javascripts/disco_app/components.js +2 -0
  39. data/app/assets/javascripts/disco_app/disco_app.js +9 -0
  40. data/app/assets/javascripts/disco_app/frame.js +152 -0
  41. data/app/assets/javascripts/disco_app/shopify-turbolinks.js +7 -0
  42. data/app/assets/javascripts/disco_app/ui-kit.js +1 -0
  43. data/app/assets/stylesheets/disco_app/admin/_header.scss +75 -0
  44. data/app/assets/stylesheets/disco_app/admin/_layout.scss +32 -0
  45. data/app/assets/stylesheets/disco_app/admin/_nav.scss +184 -0
  46. data/app/assets/stylesheets/disco_app/admin.scss +11 -0
  47. data/app/assets/stylesheets/disco_app/disco_app.scss +19 -0
  48. data/app/assets/stylesheets/disco_app/frame/_buttons.scss +54 -0
  49. data/app/assets/stylesheets/disco_app/frame/_forms.scss +26 -0
  50. data/app/assets/stylesheets/disco_app/frame/_layout.scss +77 -0
  51. data/app/assets/stylesheets/disco_app/frame/_type.scss +25 -0
  52. data/app/assets/stylesheets/disco_app/frame.scss +10 -0
  53. data/app/assets/stylesheets/disco_app/mixins/_flexbox.scss +400 -0
  54. data/app/assets/stylesheets/disco_app/ui-kit/_ui-empty-state.scss +121 -0
  55. data/app/assets/stylesheets/disco_app/ui-kit/_ui-footer-help.scss +28 -0
  56. data/app/assets/stylesheets/disco_app/ui-kit/_ui-icons.scss +28 -0
  57. data/app/assets/stylesheets/disco_app/ui-kit/_ui-kit.scss +5113 -0
  58. data/app/assets/stylesheets/disco_app/ui-kit/_ui-layout.scss +15 -0
  59. data/app/assets/stylesheets/disco_app/ui-kit/_ui-page-actions.scss +21 -0
  60. data/app/assets/stylesheets/disco_app/ui-kit/_ui-tabs.scss +75 -0
  61. data/app/assets/stylesheets/disco_app/ui-kit/_ui-type.scss +13 -0
  62. data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
  63. data/app/controllers/disco_app/admin/application_controller.rb +3 -0
  64. data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
  65. data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
  66. data/app/controllers/disco_app/admin/concerns/plans_controller.rb +54 -0
  67. data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
  68. data/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +29 -0
  69. data/app/controllers/disco_app/admin/plans_controller.rb +3 -0
  70. data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
  71. data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
  72. data/app/controllers/disco_app/admin/subscriptions_controller.rb +3 -0
  73. data/app/controllers/disco_app/charges_controller.rb +47 -0
  74. data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
  75. data/app/controllers/disco_app/concerns/authenticated_controller.rb +56 -0
  76. data/app/controllers/disco_app/concerns/carrier_request_controller.rb +21 -0
  77. data/app/controllers/disco_app/frame_controller.rb +9 -0
  78. data/app/controllers/disco_app/install_controller.rb +27 -0
  79. data/app/controllers/disco_app/subscriptions_controller.rb +32 -0
  80. data/app/controllers/disco_app/webhooks_controller.rb +46 -0
  81. data/app/controllers/sessions_controller.rb +28 -0
  82. data/app/helpers/disco_app/application_helper.rb +50 -0
  83. data/app/jobs/disco_app/app_installed_job.rb +3 -0
  84. data/app/jobs/disco_app/app_uninstalled_job.rb +3 -0
  85. data/app/jobs/disco_app/concerns/app_installed_job.rb +39 -0
  86. data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +20 -0
  87. data/app/jobs/disco_app/concerns/render_asset_group_job.rb +8 -0
  88. data/app/jobs/disco_app/concerns/shop_update_job.rb +13 -0
  89. data/app/jobs/disco_app/concerns/subscription_changed_job.rb +7 -0
  90. data/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +52 -0
  91. data/app/jobs/disco_app/concerns/synchronise_resources_job.rb +12 -0
  92. data/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +61 -0
  93. data/app/jobs/disco_app/render_asset_group_job.rb +3 -0
  94. data/app/jobs/disco_app/shop_job.rb +27 -0
  95. data/app/jobs/disco_app/shop_update_job.rb +3 -0
  96. data/app/jobs/disco_app/subscription_changed_job.rb +3 -0
  97. data/app/jobs/disco_app/synchronise_carrier_service_job.rb +3 -0
  98. data/app/jobs/disco_app/synchronise_resources_job.rb +3 -0
  99. data/app/jobs/disco_app/synchronise_webhooks_job.rb +3 -0
  100. data/app/models/disco_app/app_settings.rb +3 -0
  101. data/app/models/disco_app/application_charge.rb +18 -0
  102. data/app/models/disco_app/concerns/app_settings.rb +7 -0
  103. data/app/models/disco_app/concerns/plan.rb +26 -0
  104. data/app/models/disco_app/concerns/plan_code.rb +15 -0
  105. data/app/models/disco_app/concerns/renders_assets.rb +166 -0
  106. data/app/models/disco_app/concerns/shop.rb +68 -0
  107. data/app/models/disco_app/concerns/subscription.rb +60 -0
  108. data/app/models/disco_app/concerns/synchronises.rb +50 -0
  109. data/app/models/disco_app/concerns/taggable.rb +16 -0
  110. data/app/models/disco_app/plan.rb +3 -0
  111. data/app/models/disco_app/plan_code.rb +3 -0
  112. data/app/models/disco_app/recurring_application_charge.rb +18 -0
  113. data/app/models/disco_app/session_storage.rb +18 -0
  114. data/app/models/disco_app/shop.rb +3 -0
  115. data/app/models/disco_app/subscription.rb +3 -0
  116. data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +100 -0
  117. data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
  118. data/app/services/disco_app/carrier_request_service.rb +15 -0
  119. data/app/services/disco_app/charges_service.rb +81 -0
  120. data/app/services/disco_app/proxy_service.rb +17 -0
  121. data/app/services/disco_app/subscription_service.rb +46 -0
  122. data/app/services/disco_app/webhook_service.rb +30 -0
  123. data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
  124. data/app/views/disco_app/admin/plans/_form.html.erb +72 -0
  125. data/app/views/disco_app/admin/plans/_plan_code_fields.html.erb +15 -0
  126. data/app/views/disco_app/admin/plans/edit.html.erb +7 -0
  127. data/app/views/disco_app/admin/plans/index.html.erb +43 -0
  128. data/app/views/disco_app/admin/plans/new.html.erb +7 -0
  129. data/app/views/disco_app/admin/shops/index.html.erb +13 -0
  130. data/app/views/disco_app/admin/subscriptions/edit.html.erb +33 -0
  131. data/app/views/disco_app/charges/activate.html.erb +1 -0
  132. data/app/views/disco_app/charges/create.html.erb +1 -0
  133. data/app/views/disco_app/charges/new.html.erb +23 -0
  134. data/app/views/disco_app/frame/frame.html.erb +36 -0
  135. data/app/views/disco_app/install/installing.html.erb +7 -0
  136. data/app/views/disco_app/install/uninstalling.html.erb +1 -0
  137. data/app/views/disco_app/proxy_errors/404.html.erb +1 -0
  138. data/app/views/disco_app/shared/_card.html.erb +14 -0
  139. data/app/views/disco_app/shared/_icons.html.erb +3 -0
  140. data/app/views/disco_app/shared/_section.html.erb +17 -0
  141. data/app/views/disco_app/subscriptions/new.html.erb +25 -0
  142. data/app/views/layouts/admin/_nav_items.erb +20 -0
  143. data/app/views/layouts/admin.html.erb +67 -0
  144. data/app/views/layouts/application.html.erb +18 -0
  145. data/app/views/layouts/embedded_app.html.erb +44 -0
  146. data/app/views/layouts/embedded_app_modal.html.erb +28 -0
  147. data/app/views/sessions/new.html.erb +26 -0
  148. data/config/routes.rb +48 -0
  149. data/db/migrate/20150525000000_create_shops_if_not_existent.rb +15 -0
  150. data/db/migrate/20150525162112_add_status_to_shops.rb +5 -0
  151. data/db/migrate/20150525171422_add_meta_to_shops.rb +11 -0
  152. data/db/migrate/20150629210346_add_charge_status_to_shop.rb +5 -0
  153. data/db/migrate/20150814214025_add_more_meta_to_shops.rb +15 -0
  154. data/db/migrate/20151017231302_create_disco_app_plans.rb +13 -0
  155. data/db/migrate/20151017232027_create_disco_app_subscriptions.rb +15 -0
  156. data/db/migrate/20151017234409_move_shop_to_disco_app_engine.rb +5 -0
  157. data/db/migrate/20160112233706_create_disco_app_sessions.rb +12 -0
  158. data/db/migrate/20160113194418_add_shop_id_to_disco_app_sessions.rb +6 -0
  159. data/db/migrate/20160223111044_create_disco_app_settings.rb +8 -0
  160. data/db/migrate/20160301223215_update_plans.rb +22 -0
  161. data/db/migrate/20160301224558_update_subscriptions.rb +13 -0
  162. data/db/migrate/20160302104816_create_disco_app_recurring_application_charges.rb +14 -0
  163. data/db/migrate/20160302105259_create_disco_app_application_charges.rb +14 -0
  164. data/db/migrate/20160302134728_drop_charge_status_from_shops.rb +5 -0
  165. data/db/migrate/20160302142941_add_shopify_attributes_to_charges.rb +8 -0
  166. data/db/migrate/20160331093148_create_disco_app_plan_codes.rb +14 -0
  167. data/db/migrate/20160401044420_add_status_to_plan_codes.rb +5 -0
  168. data/db/migrate/20160401045551_add_amount_and_plan_code_to_disco_app_subscriptions.rb +7 -0
  169. data/db/migrate/20160425205211_add_source_to_disco_app_subscriptions.rb +5 -0
  170. data/db/migrate/20160426033520_add_trial_period_days_to_disco_app_subscriptions.rb +5 -0
  171. data/db/migrate/20160513140727_add_name_to_disco_app_shops.rb +5 -0
  172. data/db/migrate/20160521135510_move_shop_to_synchronises.rb +61 -0
  173. data/lib/disco_app/configuration.rb +39 -0
  174. data/lib/disco_app/constants.rb +4 -0
  175. data/lib/disco_app/engine.rb +26 -0
  176. data/lib/disco_app/session.rb +14 -0
  177. data/lib/disco_app/support/file_fixtures.rb +23 -0
  178. data/lib/disco_app/test_help.rb +11 -0
  179. data/lib/disco_app/version.rb +3 -0
  180. data/lib/disco_app.rb +7 -0
  181. data/lib/generators/disco_app/USAGE +5 -0
  182. data/lib/generators/disco_app/disco_app_generator.rb +235 -0
  183. data/lib/generators/disco_app/templates/assets/javascripts/application.js +17 -0
  184. data/lib/generators/disco_app/templates/assets/javascripts/components.js +3 -0
  185. data/lib/generators/disco_app/templates/assets/stylesheets/application.scss +5 -0
  186. data/lib/generators/disco_app/templates/config/database.yml.tt +20 -0
  187. data/lib/generators/disco_app/templates/config/newrelic.yml +26 -0
  188. data/lib/generators/disco_app/templates/config/puma.rb +15 -0
  189. data/lib/generators/disco_app/templates/controllers/home_controller.rb +7 -0
  190. data/lib/generators/disco_app/templates/initializers/disco_app.rb +19 -0
  191. data/lib/generators/disco_app/templates/initializers/rollbar.rb +12 -0
  192. data/lib/generators/disco_app/templates/initializers/session_store.rb +2 -0
  193. data/lib/generators/disco_app/templates/initializers/shopify_app.rb +7 -0
  194. data/lib/generators/disco_app/templates/initializers/shopify_session_repository.rb +7 -0
  195. data/lib/generators/disco_app/templates/root/CHECKS +4 -0
  196. data/lib/generators/disco_app/templates/root/Procfile +2 -0
  197. data/lib/generators/disco_app/templates/views/home/index.html.erb +2 -0
  198. data/lib/tasks/carrier_service.rake +10 -0
  199. data/lib/tasks/database.rake +8 -0
  200. data/lib/tasks/sessions.rake +9 -0
  201. data/lib/tasks/shops.rake +10 -0
  202. data/lib/tasks/start.rake +3 -0
  203. data/lib/tasks/webhooks.rake +10 -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 +92 -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/disco_app/admin/shops_controller.rb +8 -0
  217. data/test/dummy/app/controllers/home_controller.rb +7 -0
  218. data/test/dummy/app/controllers/proxy_controller.rb +8 -0
  219. data/test/dummy/app/helpers/application_helper.rb +2 -0
  220. data/test/dummy/app/jobs/disco_app/app_installed_job.rb +16 -0
  221. data/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +11 -0
  222. data/test/dummy/app/jobs/products_create_job.rb +7 -0
  223. data/test/dummy/app/jobs/products_delete_job.rb +7 -0
  224. data/test/dummy/app/jobs/products_update_job.rb +7 -0
  225. data/test/dummy/app/models/disco_app/shop.rb +15 -0
  226. data/test/dummy/app/models/js_configuration.rb +8 -0
  227. data/test/dummy/app/models/product.rb +6 -0
  228. data/test/dummy/app/models/widget_configuration.rb +10 -0
  229. data/test/dummy/app/views/assets/script_tag.js.erb +1 -0
  230. data/test/dummy/app/views/assets/test.js.erb +1 -0
  231. data/test/dummy/app/views/assets/widget.js.erb +2 -0
  232. data/test/dummy/app/views/assets/widget.scss.erb +3 -0
  233. data/test/dummy/app/views/home/index.html.erb +2 -0
  234. data/test/dummy/app/views/snippets/widget.liquid.erb +1 -0
  235. data/test/dummy/bin/bundle +3 -0
  236. data/test/dummy/bin/rails +4 -0
  237. data/test/dummy/bin/rake +4 -0
  238. data/test/dummy/bin/setup +29 -0
  239. data/test/dummy/config/application.rb +38 -0
  240. data/test/dummy/config/boot.rb +5 -0
  241. data/test/dummy/config/database.codeship.yml +23 -0
  242. data/test/dummy/config/database.gitlab-ci.yml +24 -0
  243. data/test/dummy/config/database.yml +20 -0
  244. data/test/dummy/config/environment.rb +5 -0
  245. data/test/dummy/config/environments/development.rb +41 -0
  246. data/test/dummy/config/environments/production.rb +85 -0
  247. data/test/dummy/config/environments/test.rb +42 -0
  248. data/test/dummy/config/initializers/assets.rb +11 -0
  249. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  250. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  251. data/test/dummy/config/initializers/disco_app.rb +19 -0
  252. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  253. data/test/dummy/config/initializers/inflections.rb +16 -0
  254. data/test/dummy/config/initializers/mime_types.rb +4 -0
  255. data/test/dummy/config/initializers/omniauth.rb +9 -0
  256. data/test/dummy/config/initializers/session_store.rb +2 -0
  257. data/test/dummy/config/initializers/shopify_app.rb +7 -0
  258. data/test/dummy/config/initializers/shopify_session_repository.rb +7 -0
  259. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  260. data/test/dummy/config/locales/en.yml +23 -0
  261. data/test/dummy/config/routes.rb +10 -0
  262. data/test/dummy/config/secrets.yml +22 -0
  263. data/test/dummy/config.ru +4 -0
  264. data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
  265. data/test/dummy/db/migrate/20160530160739_create_asset_models.rb +19 -0
  266. data/test/dummy/db/schema.rb +141 -0
  267. data/test/dummy/public/404.html +67 -0
  268. data/test/dummy/public/422.html +67 -0
  269. data/test/dummy/public/500.html +66 -0
  270. data/test/dummy/public/favicon.ico +0 -0
  271. data/test/fixtures/api/widget_store/assets/create_script_tag_js_request.json +6 -0
  272. data/test/fixtures/api/widget_store/assets/create_script_tag_js_response.json +12 -0
  273. data/test/fixtures/api/widget_store/assets/create_script_tag_request.json +6 -0
  274. data/test/fixtures/api/widget_store/assets/create_script_tag_response.json +10 -0
  275. data/test/fixtures/api/widget_store/assets/create_test_js_request.json +6 -0
  276. data/test/fixtures/api/widget_store/assets/create_test_js_response.json +12 -0
  277. data/test/fixtures/api/widget_store/assets/create_widget_js_request.json +6 -0
  278. data/test/fixtures/api/widget_store/assets/create_widget_js_response.json +12 -0
  279. data/test/fixtures/api/widget_store/assets/create_widget_liquid_request.json +6 -0
  280. data/test/fixtures/api/widget_store/assets/create_widget_liquid_response.json +12 -0
  281. data/test/fixtures/api/widget_store/assets/create_widget_scss_request.json +6 -0
  282. data/test/fixtures/api/widget_store/assets/create_widget_scss_response.json +12 -0
  283. data/test/fixtures/api/widget_store/assets/get_script_tags_empty_request.json +1 -0
  284. data/test/fixtures/api/widget_store/assets/get_script_tags_empty_response.json +1 -0
  285. data/test/fixtures/api/widget_store/assets/get_script_tags_preexisting_request.json +1 -0
  286. data/test/fixtures/api/widget_store/assets/get_script_tags_preexisting_response.json +12 -0
  287. data/test/fixtures/api/widget_store/assets/update_script_tag_request.json +10 -0
  288. data/test/fixtures/api/widget_store/assets/update_script_tag_response.json +10 -0
  289. data/test/fixtures/api/widget_store/charges/activate_application_charge_request.json +16 -0
  290. data/test/fixtures/api/widget_store/charges/activate_application_charge_response.json +1 -0
  291. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_request.json +20 -0
  292. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_response.json +1 -0
  293. data/test/fixtures/api/widget_store/charges/create_application_charge_request.json +9 -0
  294. data/test/fixtures/api/widget_store/charges/create_application_charge_response.json +16 -0
  295. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +9 -0
  296. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_response.json +20 -0
  297. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +9 -0
  298. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_response.json +20 -0
  299. data/test/fixtures/api/widget_store/charges/get_accepted_application_charge_response.json +16 -0
  300. data/test/fixtures/api/widget_store/charges/get_accepted_recurring_application_charge_response.json +20 -0
  301. data/test/fixtures/api/widget_store/charges/get_declined_application_charge_response.json +16 -0
  302. data/test/fixtures/api/widget_store/charges/get_declined_recurring_application_charge_response.json +20 -0
  303. data/test/fixtures/api/widget_store/charges/get_pending_application_charge_response.json +16 -0
  304. data/test/fixtures/api/widget_store/charges/get_pending_recurring_application_charge_response.json +20 -0
  305. data/test/fixtures/api/widget_store/shop.json +46 -0
  306. data/test/fixtures/api/widget_store/webhooks.json +1 -0
  307. data/test/fixtures/assets/test.js +1 -0
  308. data/test/fixtures/assets/test.min.js +1 -0
  309. data/test/fixtures/disco_app/application_charges.yml +11 -0
  310. data/test/fixtures/disco_app/plan_codes.yml +13 -0
  311. data/test/fixtures/disco_app/plans.yml +37 -0
  312. data/test/fixtures/disco_app/recurring_application_charges.yml +11 -0
  313. data/test/fixtures/disco_app/shops.yml +10 -0
  314. data/test/fixtures/disco_app/subscriptions.yml +22 -0
  315. data/test/fixtures/js_configurations.yml +3 -0
  316. data/test/fixtures/products.yml +4 -0
  317. data/test/fixtures/webhooks/app_uninstalled.json +46 -0
  318. data/test/fixtures/webhooks/product_created.json +167 -0
  319. data/test/fixtures/webhooks/product_deleted.json +3 -0
  320. data/test/fixtures/webhooks/product_updated.json +167 -0
  321. data/test/fixtures/widget_configurations.yml +4 -0
  322. data/test/integration/synchronises_test.rb +55 -0
  323. data/test/jobs/disco_app/app_installed_job_test.rb +55 -0
  324. data/test/jobs/disco_app/app_uninstalled_job_test.rb +30 -0
  325. data/test/models/disco_app/plan_test.rb +5 -0
  326. data/test/models/disco_app/renders_assets_test.rb +109 -0
  327. data/test/models/disco_app/session_test.rb +31 -0
  328. data/test/models/disco_app/shop_test.rb +27 -0
  329. data/test/models/disco_app/subscription_test.rb +19 -0
  330. data/test/services/disco_app/charges_service_test.rb +112 -0
  331. data/test/services/disco_app/subscription_service_test.rb +60 -0
  332. data/test/support/test_file_fixtures.rb +29 -0
  333. data/test/support/test_shopify_api.rb +16 -0
  334. data/test/test_helper.rb +55 -0
  335. metadata +830 -0
@@ -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,20 @@
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(domain, shop_data)
16
+ DiscoApp::ChargesService.cancel_recurring_charges(@shop)
17
+ @shop.sessions.delete_all
18
+ end
19
+
20
+ 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,7 @@
1
+ module DiscoApp::Concerns::SubscriptionChangedJob
2
+ extend ActiveSupport::Concern
3
+
4
+ def perform(shop, subscription)
5
+ end
6
+
7
+ end
@@ -0,0 +1,52 @@
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
+ nil
38
+ end
39
+
40
+ private
41
+
42
+ # Return a list of currently registered carrier service names.
43
+ def current_carrier_service_names
44
+ current_carrier_services.map(&:name)
45
+ end
46
+
47
+ # Return a list of currently registered carrier services.
48
+ def current_carrier_services
49
+ @current_carrier_service ||= ShopifyAPI::CarrierService.find(:all)
50
+ end
51
+
52
+ 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,61 @@
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'] + 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
+ protected
36
+
37
+ # Return a list of additional webhook topics to listen for. This method
38
+ # can be overridden in the application to provide a list of app-specific
39
+ # webhooks that should be created during synchronisation.
40
+ def topics
41
+ []
42
+ end
43
+
44
+ private
45
+
46
+ # Return a list of currently registered topics.
47
+ def current_topics
48
+ current_webhooks.map(&:topic).map(&:to_sym)
49
+ end
50
+
51
+ # Return a list of current registered webhooks.
52
+ def current_webhooks
53
+ @current_webhooks ||= ShopifyAPI::Webhook.find(:all)
54
+ end
55
+
56
+ # Return the absolute URL to the webhooks endpoint.
57
+ def webhooks_url
58
+ DiscoApp::Engine.routes.url_helpers.webhooks_url
59
+ end
60
+
61
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::RenderAssetGroupJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::RenderAssetGroupJob
3
+ 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.temp {
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,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
@@ -0,0 +1,166 @@
1
+ require 'render_anywhere'
2
+ require 'uglifier'
3
+
4
+ module DiscoApp::Concerns::RendersAssets
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include RenderAnywhere
9
+ after_commit :queue_render_asset_group_job
10
+ end
11
+
12
+ class_methods do
13
+
14
+ # Ruby "macro" that allows the definition of a number of Shopify assets that
15
+ # should be rendered and uploaded when certain attributes on the including
16
+ # class change. This assumes that the including class (1) is an ActiveRecord
17
+ # model that supports an `after_commit` callback; and (2) that the model has
18
+ # a `shop` method (or attribute) that references the DiscoApp::Shop instance
19
+ # associated with the current model.
20
+ #
21
+ # Options
22
+ #
23
+ # assets: Required. A list of asset templates to be rendered and
24
+ # uploaded to Shopify. The order of assets matters only
25
+ # in that subsequent asset templates will have access to
26
+ # the public CDN url of earlier-rendered assets through
27
+ # a "@public_urls" context variable.
28
+ #
29
+ # triggered_by: Optional. A list of attributes that should trigger the
30
+ # re-rendering and upload of the assets defined by the
31
+ # `assets` option, provided as a list of string. If
32
+ # empty or nil, nothing will be triggered.
33
+ #
34
+ # script_tags: Optional. A list of assets that should have script
35
+ # tags created or updated after being rendered to the
36
+ # storefront.
37
+ #
38
+ # minify: Optional. Whether Javascript assets should be minified
39
+ # after being rendered. Defaults to true in production
40
+ # environments, false otherwise. Note that stylesheet
41
+ # assets, when uploaded as .scss files, are
42
+ # automatically minified by Shopify, so we don't need to
43
+ # do it on our end.
44
+ #
45
+ def renders_assets(*asset_groups)
46
+ options = asset_groups.last.is_a?(Hash) ? asset_groups.pop : {}
47
+ options = renders_assets_default_options.merge(options)
48
+
49
+ # Ensure assets, triggered by and script tag options are arrays.
50
+ options[:assets] = options[:assets] ? Array(options[:assets]).map(&:to_sym) : []
51
+ options[:triggered_by] = options[:triggered_by] ? Array(options[:triggered_by]).map(&:to_s) : []
52
+ options[:script_tags] = options[:script_tags] ? Array(options[:script_tags]).map(&:to_sym) : []
53
+
54
+ asset_groups.each do |asset_group|
55
+ renderable_asset_groups[asset_group.to_sym] = options
56
+ end
57
+ end
58
+
59
+ # Return a list of renderable asset groups, along with their options.
60
+ def renderable_asset_groups
61
+ @renderable_asset_groups ||= {}
62
+ end
63
+
64
+ # Return the default options for the `renders_assets` macro.
65
+ def renders_assets_default_options
66
+ {
67
+ assets: nil,
68
+ triggered_by: nil,
69
+ script_tags: nil,
70
+ minify: Rails.env.production?
71
+ }
72
+ end
73
+
74
+ end
75
+
76
+ # Callback, triggered after a model save. Iterates through each asset group
77
+ # defined on the model and queues a render job if any of the changed
78
+ # attributes are found in the asset group's triggered_by list.
79
+ def queue_render_asset_group_job
80
+ renderable_asset_groups.each do |asset_group, options|
81
+ unless (previous_changes.keys & options[:triggered_by]).empty?
82
+ DiscoApp::RenderAssetGroupJob.perform_later(shop, self, asset_group.to_s)
83
+ end
84
+ end
85
+ end
86
+
87
+ # Copies the class-level hash of assets with symbol asset name as keys and
88
+ # their corresponding options as values to the instance.
89
+ def renderable_asset_groups
90
+ @renderable_asset_groups ||= self.class.renderable_asset_groups.dup
91
+ end
92
+
93
+ # Render the specified asset group and upload the result to Shopify.
94
+ def render_asset_group(asset_group)
95
+ options = renderable_asset_groups[asset_group]
96
+ public_urls = {}
97
+
98
+ options[:assets].each do |asset|
99
+ # Create/replace the asset via the Shopify API.
100
+ shopify_asset = shop.temp {
101
+ ShopifyAPI::Asset.create(
102
+ key: asset,
103
+ value: render_asset_group_asset(asset, public_urls, options)
104
+ )
105
+ }
106
+
107
+ # Store the public URL to this asset, so that we're able to use it in
108
+ # subsequent template renders. Adds a .css suffix to .scss assets, so that
109
+ # we use the Shopify-compiled version of any SCSS stylesheets.
110
+ if shopify_asset.public_url.present?
111
+ public_urls[asset] = shopify_asset.public_url.gsub(/\.scss\?/, '.scss.css?')
112
+ end
113
+ end
114
+
115
+ # If we specified the creation of any script tags based on newly rendered
116
+ # assets, do that now.
117
+ unless options[:script_tags].empty?
118
+ render_asset_script_tags(options, public_urls)
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ # Render an individual asset within an asset group.
125
+ def render_asset_group_asset(asset, public_urls, options)
126
+ rendered_asset = render_asset_renderer.render_to_string(
127
+ template: asset,
128
+ layout: nil,
129
+ locals: {
130
+ :"@#{self.class.name.underscore}" => self,
131
+ :@public_urls => public_urls
132
+ }
133
+ )
134
+
135
+ if should_be_minified?(asset, options)
136
+ ::Uglifier.compile(rendered_asset)
137
+ else
138
+ rendered_asset
139
+ end
140
+ end
141
+
142
+ # Return true if the given asset should be minified with Uglifier.
143
+ def should_be_minified?(asset, options)
144
+ asset.to_s.end_with?('.js') and options[:minify]
145
+ end
146
+
147
+ def render_asset_renderer
148
+ @render_asset_renderer ||= self.class.const_get('RenderingController').new
149
+ end
150
+
151
+ # Render any script tags defined by the :script_tags options that we have
152
+ # a public URL for.
153
+ def render_asset_script_tags(options, public_urls)
154
+ # Fetch the current set of script tags for the store.
155
+ current_script_tags = shop.temp { ShopifyAPI::ScriptTag.find(:all) }
156
+
157
+ # Iterate each script tag for which we have a known public URL and create
158
+ # or update the corresponding script tag resource.
159
+ public_urls.slice(*options[:script_tags]).each do |asset, public_url|
160
+ script_tag = current_script_tags.find(lambda { ShopifyAPI::ScriptTag.new(event: 'onload') }) { |script_tag| script_tag.src.include?("#{asset}?") }
161
+ script_tag.src = public_url
162
+ shop.temp { script_tag.save }
163
+ end
164
+ end
165
+
166
+ end
@@ -0,0 +1,68 @@
1
+ module DiscoApp::Concerns::Shop
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include ShopifyApp::Shop
6
+ include ActionView::Helpers::DateHelper
7
+
8
+ # Define relationships to plans and subscriptions.
9
+ has_many :subscriptions
10
+ has_many :plans, through: :subscriptions
11
+
12
+ # Define relationship to sessions.
13
+ has_many :sessions, class_name: 'DiscoApp::Session', dependent: :destroy
14
+
15
+ # Define possible installation statuses as an enum.
16
+ enum status: [:never_installed, :awaiting_install, :installing, :installed, :awaiting_uninstall, :uninstalling, :uninstalled]
17
+
18
+ # Define some useful scopes.
19
+ scope :status, -> (status) { where status: status }
20
+ scope :installed, -> { where status: statuses[:installed] }
21
+ scope :has_active_shopify_plan, -> { where.not(plan_name: [:cancelled, :frozen, :fraudulent]) }
22
+
23
+ # Alias 'with_shopify_session' as 'temp', as per our existing conventions.
24
+ alias_method :temp, :with_shopify_session
25
+
26
+ # Return true if the shop is considered as in development mode.
27
+ def development?
28
+ ['staff', 'custom'].include?(plan_name)
29
+ end
30
+
31
+ # Convenience method to check if this shop has a current subscription.
32
+ def current_subscription?
33
+ current_subscription.present?
34
+ end
35
+
36
+ # Convenience method to get the current subscription for this shop, if any.
37
+ def current_subscription
38
+ subscriptions.current.first
39
+ end
40
+
41
+ # Convenience method to get the current plan for this shop, if any.
42
+ def current_plan
43
+ current_subscription&.plan
44
+ end
45
+
46
+ # Return the absolute URL to the shop's storefront.
47
+ def url
48
+ "#{protocol}://#{domain}"
49
+ end
50
+
51
+ # Return the protocol the shop's storefront uses. This should now always be
52
+ # https as all Shopify stores have SSL enabled.
53
+ def protocol
54
+ 'https'
55
+ end
56
+
57
+ # Return the absolute URL to the shop's admin.
58
+ def admin_url
59
+ "https://#{shopify_domain}/admin"
60
+ end
61
+
62
+ def installed_duration
63
+ distance_of_time_in_words_to_now(created_at.time)
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,60 @@
1
+ module DiscoApp::Concerns::Subscription
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ belongs_to :shop
7
+ belongs_to :plan
8
+ belongs_to :plan_code
9
+
10
+ has_many :one_time_charges, class_name: 'DiscoApp::ApplicationCharge', dependent: :destroy
11
+ has_many :recurring_charges, class_name: 'DiscoApp::RecurringApplicationCharge', dependent: :destroy
12
+
13
+ enum status: [:trial, :active, :cancelled]
14
+ enum subscription_type: [:recurring, :one_time]
15
+
16
+ scope :current, -> { where status: [statuses[:trial], statuses[:active]] }
17
+
18
+ after_commit :cancel_charge
19
+
20
+ end
21
+
22
+ # Only require an active charge if the amount to be charged is > 0.
23
+ def requires_active_charge?
24
+ amount > 0
25
+ end
26
+
27
+ # Convenience method to check if this subscription has an active charge.
28
+ def active_charge?
29
+ active_charge.present?
30
+ end
31
+
32
+ # Convenience method to get the active charge for this subscription.
33
+ def active_charge
34
+ charges.active.first
35
+ end
36
+
37
+ # Return the appropriate set of charges for this subscription's type.
38
+ def charges
39
+ recurring? ? recurring_charges : one_time_charges
40
+ end
41
+
42
+ def charge_class
43
+ recurring? ? DiscoApp::RecurringApplicationCharge : DiscoApp::ApplicationCharge
44
+ end
45
+
46
+ def shopify_charge_class
47
+ recurring? ? ShopifyAPI::RecurringApplicationCharge : ShopifyAPI::ApplicationCharge
48
+ end
49
+
50
+ private
51
+
52
+ # If the amount or trial period for this subscription changes, clear any
53
+ # active charge, as the user will need to re-authorize the charge.
54
+ def cancel_charge
55
+ return if (previous_changes.keys & ['amount', 'trial_period_days']).empty?
56
+ return unless active_charge?
57
+ active_charge.cancelled!
58
+ end
59
+
60
+ end