disco_app 0.9.4

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