disco_app 0.8.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.
- checksums.yaml +7 -0
- data/Rakefile +37 -0
- data/app/assets/images/disco_app/icon.svg +1 -0
- data/app/assets/javascripts/disco_app/components/filterable_shop_list.js.jsx +65 -0
- data/app/assets/javascripts/disco_app/components/shop_filter_tab.js.jsx +34 -0
- data/app/assets/javascripts/disco_app/components/shop_filter_tabs.js.jsx +21 -0
- data/app/assets/javascripts/disco_app/components/shop_list.js.jsx +140 -0
- data/app/assets/javascripts/disco_app/components/shop_row.js.jsx +27 -0
- data/app/assets/javascripts/disco_app/components/shopify_admin_link.js.jsx +29 -0
- data/app/assets/javascripts/disco_app/components.js +5 -0
- data/app/assets/javascripts/disco_app/disco_app.js +7 -0
- data/app/assets/javascripts/disco_app/frame.js +152 -0
- data/app/assets/javascripts/disco_app/shopify-turbolinks.js +7 -0
- data/app/assets/stylesheets/disco_app/bootstrap/_custom.scss +54 -0
- data/app/assets/stylesheets/disco_app/bootstrap/_variables.scss +872 -0
- data/app/assets/stylesheets/disco_app/disco/_buttons.scss +31 -0
- data/app/assets/stylesheets/disco_app/disco/_cards.scss +51 -0
- data/app/assets/stylesheets/disco_app/disco/_forms.scss +23 -0
- data/app/assets/stylesheets/disco_app/disco/_grid.scss +58 -0
- data/app/assets/stylesheets/disco_app/disco/_sections.scss +61 -0
- data/app/assets/stylesheets/disco_app/disco/_tables.scss +57 -0
- data/app/assets/stylesheets/disco_app/disco/_tabs.scss +61 -0
- data/app/assets/stylesheets/disco_app/disco/_type.scss +39 -0
- data/app/assets/stylesheets/disco_app/disco/mixins/_flexbox.scss +394 -0
- data/app/assets/stylesheets/disco_app/disco_app.scss +16 -0
- data/app/assets/stylesheets/disco_app/frame/_buttons.scss +54 -0
- data/app/assets/stylesheets/disco_app/frame/_forms.scss +26 -0
- data/app/assets/stylesheets/disco_app/frame/_layout.scss +77 -0
- data/app/assets/stylesheets/disco_app/frame/_type.scss +32 -0
- data/app/assets/stylesheets/disco_app/frame.scss +9 -0
- data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
- data/app/controllers/disco_app/admin/application_controller.rb +3 -0
- data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
- data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
- data/app/controllers/disco_app/admin/concerns/plans_controller.rb +51 -0
- data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
- data/app/controllers/disco_app/admin/plans_controller.rb +3 -0
- data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
- data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
- data/app/controllers/disco_app/charges_controller.rb +47 -0
- data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
- data/app/controllers/disco_app/concerns/authenticated_controller.rb +56 -0
- data/app/controllers/disco_app/concerns/carrier_request_controller.rb +21 -0
- data/app/controllers/disco_app/frame_controller.rb +9 -0
- data/app/controllers/disco_app/install_controller.rb +27 -0
- data/app/controllers/disco_app/subscriptions_controller.rb +40 -0
- data/app/controllers/disco_app/webhooks_controller.rb +46 -0
- data/app/controllers/sessions_controller.rb +22 -0
- data/app/helpers/disco_app/application_helper.rb +28 -0
- data/app/jobs/disco_app/app_installed_job.rb +3 -0
- data/app/jobs/disco_app/app_uninstalled_job.rb +3 -0
- data/app/jobs/disco_app/concerns/app_installed_job.rb +39 -0
- data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +20 -0
- data/app/jobs/disco_app/concerns/shop_update_job.rb +16 -0
- data/app/jobs/disco_app/concerns/subscription_changed_job.rb +7 -0
- data/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +52 -0
- data/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +61 -0
- data/app/jobs/disco_app/shop_job.rb +27 -0
- data/app/jobs/disco_app/shop_update_job.rb +3 -0
- data/app/jobs/disco_app/subscription_changed_job.rb +3 -0
- data/app/jobs/disco_app/synchronise_carrier_service_job.rb +3 -0
- data/app/jobs/disco_app/synchronise_webhooks_job.rb +3 -0
- data/app/models/disco_app/app_settings.rb +3 -0
- data/app/models/disco_app/application_charge.rb +18 -0
- data/app/models/disco_app/concerns/app_settings.rb +7 -0
- data/app/models/disco_app/concerns/plan.rb +26 -0
- data/app/models/disco_app/concerns/plan_code.rb +15 -0
- data/app/models/disco_app/concerns/shop.rb +76 -0
- data/app/models/disco_app/concerns/subscription.rb +48 -0
- data/app/models/disco_app/concerns/synchronises.rb +39 -0
- data/app/models/disco_app/plan.rb +3 -0
- data/app/models/disco_app/plan_code.rb +3 -0
- data/app/models/disco_app/recurring_application_charge.rb +18 -0
- data/app/models/disco_app/session_storage.rb +18 -0
- data/app/models/disco_app/shop.rb +3 -0
- data/app/models/disco_app/subscription.rb +3 -0
- data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +46 -0
- data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
- data/app/services/disco_app/carrier_request_service.rb +15 -0
- data/app/services/disco_app/charges_service.rb +81 -0
- data/app/services/disco_app/proxy_service.rb +17 -0
- data/app/services/disco_app/subscription_service.rb +37 -0
- data/app/services/disco_app/webhook_service.rb +30 -0
- data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
- data/app/views/disco_app/admin/plans/_form.html.erb +27 -0
- data/app/views/disco_app/admin/plans/edit.html.erb +7 -0
- data/app/views/disco_app/admin/plans/index.html.erb +32 -0
- data/app/views/disco_app/admin/plans/new.html.erb +7 -0
- data/app/views/disco_app/admin/shops/index.html.erb +12 -0
- data/app/views/disco_app/charges/activate.html.erb +1 -0
- data/app/views/disco_app/charges/create.html.erb +1 -0
- data/app/views/disco_app/charges/new.html.erb +12 -0
- data/app/views/disco_app/frame/frame.html.erb +36 -0
- data/app/views/disco_app/install/installing.html.erb +7 -0
- data/app/views/disco_app/install/uninstalling.html.erb +1 -0
- data/app/views/disco_app/proxy_errors/404.html.erb +1 -0
- data/app/views/disco_app/shared/_card.html.erb +14 -0
- data/app/views/disco_app/shared/_section.html.erb +17 -0
- data/app/views/disco_app/subscriptions/new.html.erb +25 -0
- data/app/views/layouts/admin/_navbar.html.erb +25 -0
- data/app/views/layouts/admin.html.erb +27 -0
- data/app/views/layouts/application.html.erb +18 -0
- data/app/views/layouts/embedded_app.html.erb +41 -0
- data/app/views/layouts/embedded_app_modal.html.erb +17 -0
- data/app/views/sessions/new.html.erb +26 -0
- data/config/routes.rb +44 -0
- data/db/migrate/20150525000000_create_shops_if_not_existent.rb +15 -0
- data/db/migrate/20150525162112_add_status_to_shops.rb +5 -0
- data/db/migrate/20150525171422_add_meta_to_shops.rb +11 -0
- data/db/migrate/20150629210346_add_charge_status_to_shop.rb +5 -0
- data/db/migrate/20150814214025_add_more_meta_to_shops.rb +15 -0
- data/db/migrate/20151017231302_create_disco_app_plans.rb +13 -0
- data/db/migrate/20151017232027_create_disco_app_subscriptions.rb +15 -0
- data/db/migrate/20151017234409_move_shop_to_disco_app_engine.rb +5 -0
- data/db/migrate/20160112233706_create_disco_app_sessions.rb +12 -0
- data/db/migrate/20160113194418_add_shop_id_to_disco_app_sessions.rb +6 -0
- data/db/migrate/20160223111044_create_disco_app_settings.rb +8 -0
- data/db/migrate/20160301223215_update_plans.rb +22 -0
- data/db/migrate/20160301224558_update_subscriptions.rb +13 -0
- data/db/migrate/20160302104816_create_disco_app_recurring_application_charges.rb +14 -0
- data/db/migrate/20160302105259_create_disco_app_application_charges.rb +14 -0
- data/db/migrate/20160302134728_drop_charge_status_from_shops.rb +5 -0
- data/db/migrate/20160302142941_add_shopify_attributes_to_charges.rb +8 -0
- data/db/migrate/20160331093148_create_disco_app_plan_codes.rb +14 -0
- data/db/migrate/20160401044420_add_status_to_plan_codes.rb +5 -0
- data/db/migrate/20160401045551_add_amount_and_plan_code_to_disco_app_subscriptions.rb +7 -0
- data/lib/disco_app/configuration.rb +39 -0
- data/lib/disco_app/engine.rb +26 -0
- data/lib/disco_app/session.rb +14 -0
- data/lib/disco_app/support/file_fixtures.rb +23 -0
- data/lib/disco_app/test_help.rb +11 -0
- data/lib/disco_app/version.rb +3 -0
- data/lib/disco_app.rb +6 -0
- data/lib/generators/disco_app/USAGE +5 -0
- data/lib/generators/disco_app/adminify/adminify_generator.rb +35 -0
- data/lib/generators/disco_app/disco_app_generator.rb +164 -0
- data/lib/generators/disco_app/mailify/mailify_generator.rb +54 -0
- data/lib/generators/disco_app/monitorify/monitorify_generator.rb +28 -0
- data/lib/generators/disco_app/monitorify/templates/config/newrelic.yml +26 -0
- data/lib/generators/disco_app/monitorify/templates/initializers/rollbar.rb +12 -0
- data/lib/generators/disco_app/reactify/reactify_generator.rb +45 -0
- data/lib/generators/disco_app/templates/assets/javascripts/application.js +17 -0
- data/lib/generators/disco_app/templates/assets/stylesheets/application.scss +5 -0
- data/lib/generators/disco_app/templates/config/puma.rb +15 -0
- data/lib/generators/disco_app/templates/controllers/home_controller.rb +7 -0
- data/lib/generators/disco_app/templates/initializers/disco_app.rb +19 -0
- data/lib/generators/disco_app/templates/initializers/session_store.rb +2 -0
- data/lib/generators/disco_app/templates/initializers/shopify_app.rb +7 -0
- data/lib/generators/disco_app/templates/initializers/shopify_session_repository.rb +7 -0
- data/lib/generators/disco_app/templates/root/Procfile +2 -0
- data/lib/generators/disco_app/templates/views/home/index.html.erb +2 -0
- data/lib/tasks/carrier_service.rake +10 -0
- data/lib/tasks/sessions.rake +9 -0
- data/lib/tasks/start.rake +3 -0
- data/lib/tasks/webhooks.rake +10 -0
- data/test/controllers/disco_app/admin/shops_controller_test.rb +54 -0
- data/test/controllers/disco_app/charges_controller_test.rb +91 -0
- data/test/controllers/disco_app/install_controller_test.rb +50 -0
- data/test/controllers/disco_app/subscriptions_controller_test.rb +73 -0
- data/test/controllers/disco_app/webhooks_controller_test.rb +58 -0
- data/test/controllers/home_controller_test.rb +92 -0
- data/test/controllers/proxy_controller_test.rb +42 -0
- data/test/disco_app_test.rb +7 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +17 -0
- data/test/dummy/app/assets/stylesheets/application.scss +5 -0
- data/test/dummy/app/controllers/application_controller.rb +6 -0
- data/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +8 -0
- data/test/dummy/app/controllers/home_controller.rb +7 -0
- data/test/dummy/app/controllers/proxy_controller.rb +8 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/disco_app/app_installed_job.rb +16 -0
- data/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +11 -0
- data/test/dummy/app/jobs/products_create_job.rb +7 -0
- data/test/dummy/app/jobs/products_delete_job.rb +7 -0
- data/test/dummy/app/jobs/products_update_job.rb +7 -0
- data/test/dummy/app/models/disco_app/shop.rb +15 -0
- data/test/dummy/app/models/product.rb +6 -0
- data/test/dummy/app/views/home/index.html.erb +2 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +38 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.codeship.yml +23 -0
- data/test/dummy/config/database.yml +20 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +85 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/disco_app.rb +19 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/omniauth.rb +9 -0
- data/test/dummy/config/initializers/session_store.rb +2 -0
- data/test/dummy/config/initializers/shopify_app.rb +7 -0
- data/test/dummy/config/initializers/shopify_session_repository.rb +7 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +10 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
- data/test/dummy/db/schema.rb +138 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/fixtures/api/widget_store/charges/activate_application_charge_request.json +16 -0
- data/test/fixtures/api/widget_store/charges/activate_application_charge_response.json +1 -0
- data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_request.json +20 -0
- data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_response.json +1 -0
- data/test/fixtures/api/widget_store/charges/create_application_charge_request.json +9 -0
- data/test/fixtures/api/widget_store/charges/create_application_charge_response.json +16 -0
- data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +9 -0
- data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_response.json +20 -0
- data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +9 -0
- data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_response.json +20 -0
- data/test/fixtures/api/widget_store/charges/get_accepted_application_charge_response.json +16 -0
- data/test/fixtures/api/widget_store/charges/get_accepted_recurring_application_charge_response.json +20 -0
- data/test/fixtures/api/widget_store/charges/get_declined_application_charge_response.json +16 -0
- data/test/fixtures/api/widget_store/charges/get_declined_recurring_application_charge_response.json +20 -0
- data/test/fixtures/api/widget_store/charges/get_pending_application_charge_response.json +16 -0
- data/test/fixtures/api/widget_store/charges/get_pending_recurring_application_charge_response.json +20 -0
- data/test/fixtures/api/widget_store/shop.json +46 -0
- data/test/fixtures/api/widget_store/webhooks.json +1 -0
- data/test/fixtures/disco_app/application_charges.yml +11 -0
- data/test/fixtures/disco_app/plan_codes.yml +6 -0
- data/test/fixtures/disco_app/plans.yml +37 -0
- data/test/fixtures/disco_app/recurring_application_charges.yml +11 -0
- data/test/fixtures/disco_app/shops.yml +10 -0
- data/test/fixtures/disco_app/subscriptions.yml +21 -0
- data/test/fixtures/products.yml +4 -0
- data/test/fixtures/webhooks/app_uninstalled.json +46 -0
- data/test/fixtures/webhooks/product_created.json +167 -0
- data/test/fixtures/webhooks/product_deleted.json +3 -0
- data/test/fixtures/webhooks/product_updated.json +167 -0
- data/test/integration/synchronises_test.rb +55 -0
- data/test/jobs/disco_app/app_installed_job_test.rb +42 -0
- data/test/jobs/disco_app/app_uninstalled_job_test.rb +30 -0
- data/test/models/disco_app/plan_test.rb +5 -0
- data/test/models/disco_app/session_test.rb +31 -0
- data/test/models/disco_app/shop_test.rb +27 -0
- data/test/services/disco_app/charges_service_test.rb +104 -0
- data/test/services/disco_app/subscription_service_test.rb +59 -0
- data/test/support/test_file_fixtures.rb +29 -0
- data/test/support/test_shopify_api.rb +16 -0
- data/test/test_helper.rb +52 -0
- metadata +660 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module DiscoApp::Admin::Concerns::PlansController
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :find_plan, only: [:edit, :update]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@plans = DiscoApp::Plan.all
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new
|
|
13
|
+
@plan = DiscoApp::Plan.new
|
|
14
|
+
@plan.plan_codes.build
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
@plan = DiscoApp::Plan.new(plan_params)
|
|
19
|
+
if @plan.save
|
|
20
|
+
redirect_to admin_plans_path
|
|
21
|
+
else
|
|
22
|
+
render 'new'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def edit
|
|
27
|
+
@plan.plan_codes.build
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update
|
|
31
|
+
if @plan.update_attributes(plan_params)
|
|
32
|
+
redirect_to admin_plans_path
|
|
33
|
+
else
|
|
34
|
+
render 'edit'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def find_plan
|
|
41
|
+
@plan = DiscoApp::Plan.find(params[:id])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def plan_params
|
|
45
|
+
params.require(:plan).permit(
|
|
46
|
+
:name, :status, :plan_type, :trial_period_days, :amount,
|
|
47
|
+
:plan_codes_attributes => [:code, :trial_period_days, :amount]
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class DiscoApp::ChargesController < ApplicationController
|
|
2
|
+
include DiscoApp::Concerns::AuthenticatedController
|
|
3
|
+
|
|
4
|
+
skip_before_action :check_active_charge
|
|
5
|
+
before_action :find_subscription
|
|
6
|
+
|
|
7
|
+
# Display a "pre-charge" page, giving the opportunity to explain why a charge
|
|
8
|
+
# needs to be made.
|
|
9
|
+
def new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Attempt to create a new charge for the logged in shop and selected
|
|
13
|
+
# subscription. If successful, redirect to the (external) charge confirmation
|
|
14
|
+
# URL. If it fails, redirect back to the new charge page.
|
|
15
|
+
def create
|
|
16
|
+
if(charge = DiscoApp::ChargesService.create(@shop, @subscription)).nil?
|
|
17
|
+
redirect_to action: :new
|
|
18
|
+
else
|
|
19
|
+
redirect_to charge.confirmation_url
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Attempt to activate a charge after a user has accepted or declined it.
|
|
24
|
+
# Redirect to the main application's root URL immediately afterwards - if the
|
|
25
|
+
# charge wasn't accepted, the flow will start again.
|
|
26
|
+
def activate
|
|
27
|
+
# First attempt to find a matching charge.
|
|
28
|
+
if(charge = @subscription.charges.find_by(id: params[:id], shopify_id: params[:charge_id])).nil?
|
|
29
|
+
redirect_to action: :new and return
|
|
30
|
+
end
|
|
31
|
+
if DiscoApp::ChargesService.activate(@shop, charge)
|
|
32
|
+
redirect_to main_app.root_url
|
|
33
|
+
else
|
|
34
|
+
redirect_to action: :new
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def find_subscription
|
|
41
|
+
@subscription = @shop.subscriptions.find_by_id!(params[:subscription_id])
|
|
42
|
+
unless @subscription.requires_active_charge? and not @subscription.active_charge?
|
|
43
|
+
redirect_to main_app.root_url
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module DiscoApp::Concerns::AppProxyController
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :verify_proxy_signature
|
|
6
|
+
before_action :shopify_shop
|
|
7
|
+
after_action :add_liquid_header
|
|
8
|
+
|
|
9
|
+
rescue_from ActiveRecord::RecordNotFound do |exception|
|
|
10
|
+
render_error 404
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def verify_proxy_signature
|
|
17
|
+
unless proxy_signature_is_valid?
|
|
18
|
+
head :unauthorized
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def proxy_signature_is_valid?
|
|
23
|
+
return true if Rails.env.development? and DiscoApp.configuration.skip_proxy_verification?
|
|
24
|
+
DiscoApp::ProxyService.proxy_signature_is_valid?(request.query_string, ShopifyApp.configuration.secret)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def shopify_shop
|
|
28
|
+
@shop = DiscoApp::Shop.find_by_shopify_domain!(params[:shop])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_liquid_header
|
|
32
|
+
response.headers['Content-Type'] = 'application/liquid'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def render_error(status)
|
|
36
|
+
add_liquid_header
|
|
37
|
+
render "disco_app/proxy_errors/#{status}", status: status
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module DiscoApp::Concerns::AuthenticatedController
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :login_again_if_different_shop
|
|
6
|
+
before_action :shopify_shop
|
|
7
|
+
before_action :check_installed
|
|
8
|
+
before_action :check_current_subscription
|
|
9
|
+
before_action :check_active_charge
|
|
10
|
+
around_filter :shopify_session
|
|
11
|
+
layout 'embedded_app'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def shopify_shop
|
|
17
|
+
if shop_session
|
|
18
|
+
@shop = DiscoApp::Shop.find_by!(shopify_domain: @shop_session.url)
|
|
19
|
+
else
|
|
20
|
+
redirect_to_login
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_installed
|
|
25
|
+
if @shop.awaiting_install? or @shop.installing?
|
|
26
|
+
redirect_if_not_current_path disco_app.installing_path
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
if @shop.awaiting_uninstall? or @shop.uninstalling?
|
|
30
|
+
redirect_if_not_current_path disco_app.uninstalling_path
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
unless @shop.installed?
|
|
34
|
+
redirect_if_not_current_path disco_app.install_path
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def check_current_subscription
|
|
39
|
+
unless @shop.current_subscription?
|
|
40
|
+
redirect_if_not_current_path disco_app.new_subscription_path
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def check_active_charge
|
|
45
|
+
if @shop.current_subscription? and @shop.current_subscription.requires_active_charge? and not @shop.current_subscription.active_charge?
|
|
46
|
+
redirect_if_not_current_path disco_app.new_subscription_charge_path(@shop.current_subscription)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def redirect_if_not_current_path(target)
|
|
51
|
+
if request.path != target
|
|
52
|
+
redirect_to target
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module DiscoApp::Concerns::CarrierRequestController
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :verify_carrier_request
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def verify_carrier_request
|
|
11
|
+
unless carrier_request_signature_is_valid?
|
|
12
|
+
head :unauthorized
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def carrier_request_signature_is_valid?
|
|
17
|
+
return true if Rails.env.development? and DiscoApp.configuration.skip_carrier_request_verification?
|
|
18
|
+
DiscoApp::CarrierRequestService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class DiscoApp::InstallController < ApplicationController
|
|
2
|
+
include DiscoApp::Concerns::AuthenticatedController
|
|
3
|
+
|
|
4
|
+
skip_before_action :check_current_subscription
|
|
5
|
+
skip_before_action :check_active_charge
|
|
6
|
+
|
|
7
|
+
# Start the installation process for the current shop, then redirect to the installing screen.
|
|
8
|
+
def install
|
|
9
|
+
DiscoApp::AppInstalledJob.perform_later(@shop.shopify_domain)
|
|
10
|
+
redirect_to action: :installing
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Display an "installing" page.
|
|
14
|
+
def installing
|
|
15
|
+
if @shop.installed?
|
|
16
|
+
redirect_to main_app.root_path
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Display an "uninstalling" page. Should be almost never used.
|
|
21
|
+
def uninstalling
|
|
22
|
+
if @shop.uninstalled?
|
|
23
|
+
redirect_to main_app.root_path
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class DiscoApp::SubscriptionsController < ApplicationController
|
|
2
|
+
include DiscoApp::Concerns::AuthenticatedController
|
|
3
|
+
|
|
4
|
+
skip_before_action :check_current_subscription
|
|
5
|
+
|
|
6
|
+
def new
|
|
7
|
+
@subscription = DiscoApp::Subscription.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
# Get the selected plan. If it's not available or couldn't be found,
|
|
12
|
+
# redirect back to the plan selection page.
|
|
13
|
+
if(plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan])).nil?
|
|
14
|
+
redirect_to action: :new and return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# If a plan code was provided, check that it's (a) valid and available and
|
|
18
|
+
# (b) valid for the selected plan.
|
|
19
|
+
plan_code = nil
|
|
20
|
+
if subscription_params[:plan_code].present?
|
|
21
|
+
if(plan_code = DiscoApp::PlanCode.available.find_by(plan: plan, code: subscription_params[:plan_code])).nil?
|
|
22
|
+
redirect_to action: :new and return
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Subscribe the current shop to the selected plan.
|
|
27
|
+
if(subscription = DiscoApp::SubscriptionService.subscribe(@shop, plan, plan_code)).nil?
|
|
28
|
+
redirect_to action: :new
|
|
29
|
+
else
|
|
30
|
+
redirect_to main_app.root_path
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def subscription_params
|
|
37
|
+
params.require(:subscription).permit(:plan, :plan_code)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module DiscoApp
|
|
2
|
+
class WebhooksController < ActionController::Base
|
|
3
|
+
|
|
4
|
+
before_action :verify_webhook
|
|
5
|
+
|
|
6
|
+
def process_webhook
|
|
7
|
+
# Get the topic and domain for this webhook.
|
|
8
|
+
topic = request.headers['HTTP_X_SHOPIFY_TOPIC']
|
|
9
|
+
domain = request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']
|
|
10
|
+
|
|
11
|
+
# Ensure a domain was provided in the headers.
|
|
12
|
+
unless domain
|
|
13
|
+
head :bad_request
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Try to find a matching background job task for the given topic using class name.
|
|
17
|
+
job_class = DiscoApp::WebhookService.find_job_class(topic)
|
|
18
|
+
|
|
19
|
+
# Return bad request if we couldn't match a job class.
|
|
20
|
+
unless job_class.present?
|
|
21
|
+
head :bad_request
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Decode the body data and enqueue the appropriate job.
|
|
25
|
+
data = ActiveSupport::JSON::decode(request.body.read).with_indifferent_access
|
|
26
|
+
job_class.perform_later(domain, data)
|
|
27
|
+
|
|
28
|
+
render nothing: true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def verify_webhook
|
|
34
|
+
unless webhook_is_valid?
|
|
35
|
+
head :unauthorized
|
|
36
|
+
end
|
|
37
|
+
request.body.rewind
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def webhook_is_valid?
|
|
41
|
+
return true if Rails.env.development? and DiscoApp.configuration.skip_webhook_verification?
|
|
42
|
+
DiscoApp::WebhookService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class SessionsController < ApplicationController
|
|
2
|
+
include ShopifyApp::SessionsController
|
|
3
|
+
|
|
4
|
+
protected
|
|
5
|
+
|
|
6
|
+
# Override the authenticate method to allow skipping OAuth in development
|
|
7
|
+
# mode. Skipping OAuth still requires a shop with Shopify domain specified
|
|
8
|
+
# by the `shop` parameter to be present in the local database.
|
|
9
|
+
def authenticate
|
|
10
|
+
if Rails.env.development? and DiscoApp.configuration.skip_oauth?
|
|
11
|
+
shop = DiscoApp::Shop.find_by_shopify_domain!(sanitized_shop_name)
|
|
12
|
+
|
|
13
|
+
sess = ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token)
|
|
14
|
+
session[:shopify] = ShopifyApp::SessionRepository.store(sess)
|
|
15
|
+
session[:shopify_domain] = sanitized_shop_name
|
|
16
|
+
|
|
17
|
+
redirect_to disco_app.frame_path and return
|
|
18
|
+
end
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
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(shopify_domain)
|
|
18
|
+
DiscoApp::SynchroniseWebhooksJob.perform_now(shopify_domain)
|
|
19
|
+
DiscoApp::SynchroniseCarrierServiceJob.perform_now(shopify_domain)
|
|
20
|
+
DiscoApp::ShopUpdateJob.perform_now(shopify_domain)
|
|
21
|
+
|
|
22
|
+
@shop.reload
|
|
23
|
+
|
|
24
|
+
if default_plan.present?
|
|
25
|
+
DiscoApp::SubscriptionService.subscribe(@shop, default_plan)
|
|
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,16 @@
|
|
|
1
|
+
module DiscoApp::Concerns::ShopUpdateJob
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
# Perform an update of the current shop's information.
|
|
5
|
+
def perform(shopify_domain, 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
|
+
# Ensure we can access shop data through symbols.
|
|
10
|
+
shop_data = HashWithIndifferentAccess.new(shop_data)
|
|
11
|
+
|
|
12
|
+
# Update model attributes present in both our model and the data hash.
|
|
13
|
+
@shop.update_attributes(shop_data.except(:id, :created_at).slice(*DiscoApp::Shop.column_names))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
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(shopify_domain)
|
|
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(shopify_domain)
|
|
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,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 ||= 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
|