disco_app 0.4.2

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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +37 -0
  3. data/app/assets/javascripts/disco_app/disco_app.js +7 -0
  4. data/app/assets/stylesheets/disco_app/bootstrap-custom.scss +55 -0
  5. data/app/assets/stylesheets/disco_app/bootstrap-variables.scss +12 -0
  6. data/app/assets/stylesheets/disco_app/disco_app.scss +7 -0
  7. data/app/controllers/disco_app/app_proxy_controller.rb +41 -0
  8. data/app/controllers/disco_app/authenticated_controller.rb +44 -0
  9. data/app/controllers/disco_app/charges_controller.rb +30 -0
  10. data/app/controllers/disco_app/install_controller.rb +26 -0
  11. data/app/controllers/disco_app/webhooks_controller.rb +40 -0
  12. data/app/helpers/disco_app/application_helper.rb +4 -0
  13. data/app/jobs/disco_app/app_installed_job.rb +41 -0
  14. data/app/jobs/disco_app/app_uninstalled_job.rb +18 -0
  15. data/app/jobs/disco_app/shop_job.rb +29 -0
  16. data/app/jobs/disco_app/shop_update_job.rb +16 -0
  17. data/app/models/disco_app/shop.rb +39 -0
  18. data/app/services/disco_app/charges_service.rb +73 -0
  19. data/app/views/disco_app/charges/activate.html.erb +1 -0
  20. data/app/views/disco_app/charges/create.html.erb +1 -0
  21. data/app/views/disco_app/charges/new.html.erb +45 -0
  22. data/app/views/disco_app/install/installing.html.erb +7 -0
  23. data/app/views/disco_app/install/uninstalling.html.erb +1 -0
  24. data/app/views/disco_app/proxy_errors/404.html.erb +1 -0
  25. data/config/routes.rb +19 -0
  26. data/db/migrate/20150525162112_add_status_to_shops.rb +5 -0
  27. data/db/migrate/20150525171422_add_meta_to_shops.rb +11 -0
  28. data/db/migrate/20150629210346_add_charge_status_to_shop.rb +5 -0
  29. data/lib/disco_app/engine.rb +6 -0
  30. data/lib/disco_app/version.rb +3 -0
  31. data/lib/disco_app.rb +4 -0
  32. data/lib/generators/disco_app/USAGE +5 -0
  33. data/lib/generators/disco_app/disco_app_generator.rb +182 -0
  34. data/lib/generators/disco_app/reactify/reactify_generator.rb +31 -0
  35. data/lib/generators/disco_app/templates/assets/javascripts/application.js +17 -0
  36. data/lib/generators/disco_app/templates/assets/stylesheets/application.scss +5 -0
  37. data/lib/generators/disco_app/templates/config/puma.rb +15 -0
  38. data/lib/generators/disco_app/templates/controllers/home_controller.rb +7 -0
  39. data/lib/generators/disco_app/templates/initializers/disco_app.rb +1 -0
  40. data/lib/generators/disco_app/templates/initializers/shopify_app.rb +6 -0
  41. data/lib/generators/disco_app/templates/jobs/app_installed_job.rb +2 -0
  42. data/lib/generators/disco_app/templates/jobs/app_uninstalled_job.rb +2 -0
  43. data/lib/generators/disco_app/templates/jobs/shop_update_job.rb +2 -0
  44. data/lib/generators/disco_app/templates/models/shop.rb +3 -0
  45. data/lib/generators/disco_app/templates/root/Procfile +2 -0
  46. data/lib/generators/disco_app/templates/views/home/index.html.erb +2 -0
  47. data/lib/generators/disco_app/templates/views/layouts/application.html.erb +18 -0
  48. data/lib/generators/disco_app/templates/views/layouts/embedded_app.html.erb +33 -0
  49. data/lib/generators/disco_app/templates/views/sessions/new.html.erb +26 -0
  50. data/test/disco_app_test.rb +7 -0
  51. data/test/dummy/README.rdoc +28 -0
  52. data/test/dummy/Rakefile +6 -0
  53. data/test/dummy/app/assets/javascripts/application.js +13 -0
  54. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  55. data/test/dummy/app/controllers/application_controller.rb +5 -0
  56. data/test/dummy/app/helpers/application_helper.rb +2 -0
  57. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  58. data/test/dummy/bin/bundle +3 -0
  59. data/test/dummy/bin/rails +4 -0
  60. data/test/dummy/bin/rake +4 -0
  61. data/test/dummy/bin/setup +29 -0
  62. data/test/dummy/config/application.rb +26 -0
  63. data/test/dummy/config/boot.rb +5 -0
  64. data/test/dummy/config/database.yml +25 -0
  65. data/test/dummy/config/environment.rb +5 -0
  66. data/test/dummy/config/environments/development.rb +41 -0
  67. data/test/dummy/config/environments/production.rb +79 -0
  68. data/test/dummy/config/environments/test.rb +42 -0
  69. data/test/dummy/config/initializers/assets.rb +11 -0
  70. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  71. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  72. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  73. data/test/dummy/config/initializers/inflections.rb +16 -0
  74. data/test/dummy/config/initializers/mime_types.rb +4 -0
  75. data/test/dummy/config/initializers/session_store.rb +3 -0
  76. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  77. data/test/dummy/config/locales/en.yml +23 -0
  78. data/test/dummy/config/routes.rb +4 -0
  79. data/test/dummy/config/secrets.yml +22 -0
  80. data/test/dummy/config.ru +4 -0
  81. data/test/dummy/public/404.html +67 -0
  82. data/test/dummy/public/422.html +67 -0
  83. data/test/dummy/public/500.html +66 -0
  84. data/test/dummy/public/favicon.ico +0 -0
  85. data/test/integration/navigation_test.rb +10 -0
  86. data/test/lib/generators/disco_app/disco_app_generator_test.rb +16 -0
  87. data/test/test_helper.rb +20 -0
  88. metadata +336 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bfde7107b9cdb0de640f3358353f9c34a4a7daab73625a233970d4be0f914f64
4
+ data.tar.gz: a592683bdc19557edb0024bd2f9266701b51dc053e2b2f6b0ad3d7335411e41f
5
+ SHA512:
6
+ metadata.gz: d19fb76b17ecb36b93cbeee6c2939c0a83f8c028dbb0199f52c941dd9edda4319c1a85b864a167c87562a9394d5bf39fdf6e5fafbb4db39174cccd0646cb0e8e
7
+ data.tar.gz: c98f03e3c2119ed8ea4047371217055caede069cb528a4b741a8149bf8492ba68c58f83b7c8935bb69485353e19b546c3fa35abcfa93598430a5130d516c0a9f
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'DiscoApp'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,7 @@
1
+ /**
2
+ * disco_app/disco_app.js
3
+ * Base Javascript for Disco applications.
4
+ * Assumes that jQuery will be loaded in the parent application.js.
5
+ */
6
+ //= require bootstrap-sprockets
7
+ //= require_tree .
@@ -0,0 +1,55 @@
1
+ /*!
2
+ * bootstrap-custom.scss
3
+ * Customise the bootstrap components we pull in styles for.
4
+ */
5
+
6
+ // Core variables and mixins
7
+ @import "bootstrap/variables";
8
+ @import "bootstrap/mixins";
9
+
10
+ // Reset and dependencies
11
+ @import "bootstrap/normalize";
12
+ //@import "bootstrap/print";
13
+ //@import "bootstrap/glyphicons";
14
+
15
+ // Core CSS
16
+ @import "bootstrap/scaffolding";
17
+ @import "bootstrap/type";
18
+ //@import "bootstrap/code";
19
+ @import "bootstrap/grid";
20
+ @import "bootstrap/tables";
21
+ @import "bootstrap/forms";
22
+ @import "bootstrap/buttons";
23
+
24
+ // Components
25
+ @import "bootstrap/component-animations";
26
+ @import "bootstrap/dropdowns";
27
+ @import "bootstrap/button-groups";
28
+ @import "bootstrap/input-groups";
29
+ @import "bootstrap/navs";
30
+ @import "bootstrap/navbar";
31
+ //@import "bootstrap/breadcrumbs";
32
+ //@import "bootstrap/pagination";
33
+ //@import "bootstrap/pager";
34
+ @import "bootstrap/labels";
35
+ @import "bootstrap/badges";
36
+ //@import "bootstrap/jumbotron";
37
+ @import "bootstrap/thumbnails";
38
+ @import "bootstrap/alerts";
39
+ @import "bootstrap/progress-bars";
40
+ @import "bootstrap/media";
41
+ @import "bootstrap/list-group";
42
+ @import "bootstrap/panels";
43
+ //@import "bootstrap/responsive-embed";
44
+ //@import "bootstrap/wells";
45
+ @import "bootstrap/close";
46
+
47
+ // Components w/ JavaScript
48
+ //@import "bootstrap/modals";
49
+ //@import "bootstrap/tooltip";
50
+ //@import "bootstrap/popovers";
51
+ //@import "bootstrap/carousel";
52
+
53
+ // Utility classes
54
+ @import "bootstrap/utilities";
55
+ @import "bootstrap/responsive-utilities";
@@ -0,0 +1,12 @@
1
+ /*!
2
+ * bootstrap-variables.scss
3
+ * Set custom values for Bootstrap variables.
4
+ */
5
+
6
+ // Grid - change to a 24-column grid for enhanced flexibility.
7
+ $grid-columns: 24;
8
+ $grid-gutter-width: 12px;
9
+
10
+ // Colors
11
+ $brand-primary: #479ccf;
12
+ $brand-danger: #ff5d5d;
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * disco_app/disco_app.scss
3
+ * Base styles for Disco applications.
4
+ */
5
+ @import 'bootstrap-sprockets';
6
+ @import 'bootstrap-variables';
7
+ @import 'bootstrap-custom';
@@ -0,0 +1,41 @@
1
+ module DiscoApp
2
+ module AppProxyController
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :verify_proxy_signature
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 unless Rails.env.production?
24
+ query_hash = Rack::Utils.parse_query(request.query_string)
25
+ signature = query_hash.delete("signature")
26
+ sorted_params = query_hash.collect{ |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join
27
+ calculated_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha256'), ShopifyApp.configuration.secret, sorted_params)
28
+ signature == calculated_signature
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
41
+ end
@@ -0,0 +1,44 @@
1
+ module DiscoApp
2
+ module AuthenticatedController
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :login_again_if_different_shop
7
+ before_action :shopify_shop
8
+ before_action :verify_status
9
+ around_filter :shopify_session
10
+ layout 'embedded_app'
11
+ end
12
+
13
+ private
14
+
15
+ def shopify_shop
16
+ if shop_session
17
+ @shop = ::Shop.find_by!(shopify_domain: @shop_session.url)
18
+ else
19
+ redirect_to_login
20
+ end
21
+ end
22
+
23
+ def verify_status
24
+ if not (@shop.charge_active? or @shop.charge_waived?)
25
+ redirect_if_not_current_path(disco_app.new_charge_path)
26
+ elsif @shop.charge_accepted?
27
+ redirect_if_not_current_path(disco_app.activate_charge_path)
28
+ elsif @shop.never_installed? or @shop.uninstalled?
29
+ redirect_if_not_current_path(disco_app.install_path)
30
+ elsif @shop.awaiting_install? or @shop.installing?
31
+ redirect_if_not_current_path(disco_app.installing_path)
32
+ elsif @shop.awaiting_uninstall? or @shop.uninstalling?
33
+ redirect_if_not_current_path(disco_app.uninstalling_path)
34
+ end
35
+ end
36
+
37
+ def redirect_if_not_current_path(target)
38
+ if request.path != target
39
+ redirect_to target
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ module DiscoApp
2
+ class ChargesController < ApplicationController
3
+ include DiscoApp::AuthenticatedController
4
+
5
+ skip_before_action :verify_status, only: [:create, :activate]
6
+
7
+ # Display a "pre-charge" page, giving the opportunity to explain why a charge needs to be made.
8
+ def new
9
+ end
10
+
11
+ # Create a new charge for the currently logged in shop, then redirect to the charge's confirmation URL.
12
+ def create
13
+ if (shopify_charge = DiscoApp::ChargesService.create(@shop)).nil?
14
+ redirect_to action: :new and return
15
+ end
16
+ redirect_to shopify_charge.confirmation_url
17
+ end
18
+
19
+ # Attempt to activate a charge after a user has accepted or declined it. Redirect to the main application's root URL
20
+ # immediately afterwards - if the charge wasn't accepted, the flow will start again.
21
+ def activate
22
+ if (shopify_charge = DiscoApp::ChargesService.get_accepted_charge(@shop, params[:charge_id])).nil?
23
+ redirect_to action: :new and return
24
+ end
25
+ DiscoApp::ChargesService.activate(@shop, shopify_charge)
26
+ redirect_to main_app.root_url
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module DiscoApp
2
+ class InstallController < ApplicationController
3
+ include DiscoApp::AuthenticatedController
4
+
5
+ # Start the installation process for the current shop, then redirect to the installing screen.
6
+ def install
7
+ ::AppInstalledJob.perform_later(@shop.shopify_domain)
8
+ redirect_to action: :installing
9
+ end
10
+
11
+ # Display an "installing" page.
12
+ def installing
13
+ if @shop.installed?
14
+ redirect_to main_app.root_path
15
+ end
16
+ end
17
+
18
+ # Display an "uninstalling" page. Should be almost never used.
19
+ def uninstalling
20
+ if @shop.uninstalled?
21
+ redirect_to main_app.root_path
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,40 @@
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
+ # Try to find a matching background job task for the given topic using class name.
12
+ begin
13
+ job_class = "#{topic}_job".gsub('/', '_').classify.constantize
14
+ rescue NameError
15
+ head :bad_request
16
+ end
17
+
18
+ # Decode the body data and enqueue the appropriate job.
19
+ data = ActiveSupport::JSON::decode(request.body.read)
20
+ job_class.perform_later(domain, data)
21
+
22
+ render nothing: true
23
+ end
24
+
25
+ private
26
+
27
+ # Verify a webhook request.
28
+ def verify_webhook
29
+ data = request.body.read.to_s
30
+ hmac_header = request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
31
+ digest = OpenSSL::Digest::Digest.new('sha256')
32
+ calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, ShopifyApp.configuration.secret, data)).strip
33
+ unless calculated_hmac == hmac_header
34
+ head :unauthorized
35
+ end
36
+ request.body.rewind
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ module DiscoApp
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,41 @@
1
+ module DiscoApp
2
+ class AppInstalledJob < DiscoApp::ShopJob
3
+
4
+ before_enqueue { @shop.awaiting_install! }
5
+ before_perform { @shop.installing! }
6
+ after_perform { @shop.installed! }
7
+
8
+ def perform(domain)
9
+
10
+ # Install webhooks.
11
+ (base_webhook_topics + webhook_topics).each do |topic|
12
+ ShopifyAPI::Webhook.create(topic: topic, address: webhooks_url, format: 'json')
13
+ end
14
+
15
+ # Perform initial update of shop information.
16
+ ::ShopUpdateJob.perform_now(domain)
17
+
18
+ end
19
+
20
+ protected
21
+
22
+ # Return a list of additional webhook topics to listen for.
23
+ # This method should be overridden in the application.
24
+ def webhook_topics
25
+ []
26
+ end
27
+
28
+ private
29
+
30
+ # Return a list of webhook topics that will always be set up for the application.
31
+ def base_webhook_topics
32
+ [:'app/uninstalled', :'shop/update']
33
+ end
34
+
35
+ # Return the absolute URL to the webhooks endpoint.
36
+ def webhooks_url
37
+ DiscoApp::Engine.routes.url_helpers.webhooks_url
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ module DiscoApp
2
+ class AppUninstalledJob < DiscoApp::ShopJob
3
+
4
+ before_enqueue { @shop.awaiting_uninstall! }
5
+ before_perform { @shop.uninstalling! }
6
+ after_perform { @shop.uninstalled! }
7
+
8
+ def perform(domain, shop_data)
9
+
10
+ # Mark the shop's charge status as "cancelled" unless charges have been waived.
11
+ unless @shop.charge_waived?
12
+ @shop.charge_cancelled!
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ # The base class for all jobs that should be performed in the context of a particular Shop's API session. The first
2
+ # argument to any job inheriting from this class must be the domain of the relevant store, so that the appropriate
3
+ # Shop model can be fetched and the temporary API session created.
4
+
5
+ module DiscoApp
6
+ class ShopJob < ActiveJob::Base
7
+
8
+ queue_as :default
9
+
10
+ before_perform { |job| find_shop(job) }
11
+ before_enqueue { |job| find_shop(job) }
12
+
13
+ around_enqueue { |job, block| shop_context(job, block) }
14
+ around_perform { |job, block| shop_context(job, block) }
15
+
16
+ private
17
+
18
+ def find_shop(job)
19
+ @shop ||= ::Shop.find_by!(shopify_domain: job.arguments.first)
20
+ end
21
+
22
+ def shop_context(job, block)
23
+ @shop.temp {
24
+ block.call(job.arguments)
25
+ }
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ module DiscoApp
2
+ class ShopUpdateJob < DiscoApp::ShopJob
3
+
4
+ def perform(domain, shop_data = nil)
5
+ # If we weren't provided with shop data (eg from a webhook), fetch it.
6
+ shop_data ||= ActiveSupport::JSON::decode(ShopifyAPI::Shop.current.to_json)
7
+
8
+ # Ensure we can access shop data through symbols.
9
+ shop_data = HashWithIndifferentAccess.new(shop_data)
10
+
11
+ # Update model attributes present in both our model and the data hash.
12
+ @shop.update_attributes(shop_data.except(:id, :created_at).slice(*::Shop.column_names))
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module DiscoApp
2
+ module Shop
3
+ extend ActiveSupport::Concern
4
+
5
+ # Include the base ShopifyApp functionality.
6
+ include ShopifyApp::Shop
7
+
8
+ included do
9
+ # Define possible installation statuses as an enum.
10
+ enum status: [:never_installed, :awaiting_install, :installing, :installed, :awaiting_uninstall, :uninstalling, :uninstalled]
11
+
12
+ # Define possible charge statuses as an enum.
13
+ enum charge_status: [:charge_none, :charge_pending, :charge_accepted, :charge_declined, :charge_active, :charge_cancelled, :charge_waived]
14
+
15
+ # Alias 'with_shopify_session' as 'temp', as per our existing conventions.
16
+ alias_method :temp, :with_shopify_session
17
+ end
18
+
19
+ # Return a hash of attributes that should be used to create a new charge for this shop.
20
+ # This method can be overridden by the inheriting Shop class in order to provide charges
21
+ # customised to a particular shop. Otherwise, the default settings configured in application.rb
22
+ # will be used.
23
+ def new_charge_attributes
24
+ {
25
+ type: Rails.configuration.x.shopify_charges_default_type,
26
+ name: Rails.configuration.x.shopify_app_name,
27
+ price: Rails.configuration.x.shopify_charges_default_price,
28
+ trial_days: Rails.configuration.x.shopify_charges_default_trial_days,
29
+ }
30
+ end
31
+
32
+ # Update this Shop's charge_status attribute based on the given Shopify charge object.
33
+ def update_charge_status(shopify_charge)
34
+ status_update_method_name = "charge_#{shopify_charge.status}!"
35
+ self.public_send(status_update_method_name) if self.respond_to? status_update_method_name
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,73 @@
1
+ module DiscoApp
2
+ class ChargesService
3
+
4
+ # Create a new charge for the given Shop using the Shopify API.
5
+ #
6
+ # The attributes of the charge are fetched using the shop's `new_charge_attributes` method, which can be overriden
7
+ # to provide custom charge types for individual shops.
8
+ #
9
+ # Returns the new Shopify charge model on success, nil otherwise.
10
+ def self.create(shop)
11
+ shopify_charge = shop.temp {
12
+ self.charge_api_class(shop).create(self.new_charge_attributes(shop))
13
+ }
14
+
15
+ # If the charge was successfully created, update the charge status on the shop.
16
+ shop.update_charge_status(shopify_charge) if shopify_charge
17
+
18
+ # Return the charge.
19
+ shopify_charge
20
+ end
21
+
22
+ # Fetch the specified charge for the given Shop using the Shopify API and check that it has been actioned (either
23
+ # accepted or declined). Updates the shop object's charge status, then returns the charge if it was accepted or
24
+ # nil otherwise.
25
+ def self.get_accepted_charge(shop, charge_id)
26
+ begin
27
+ shopify_charge = shop.temp {
28
+ self.charge_api_class(shop).find(charge_id)
29
+ }
30
+
31
+ # If the charge was successfully fetched, update the status for the shop accordingly.
32
+ shop.update_charge_status(shopify_charge) if shopify_charge
33
+
34
+ shopify_charge
35
+ rescue
36
+ nil
37
+ end
38
+ end
39
+
40
+ # Attempt to activate the given Shopify charge for the given Shop using the Shopify API.
41
+ # Returns true on successful activation, false otherwise.
42
+ def self.activate(shop, shopify_charge)
43
+ begin
44
+ shop.temp {
45
+ shopify_charge.activate
46
+ }
47
+ shop.charge_active!
48
+ true
49
+ rescue
50
+ false
51
+ end
52
+ end
53
+
54
+ # Merge the new_charge_attributes returned by the given shop model and merge them with some application-level
55
+ # charge attributes.
56
+ def self.new_charge_attributes(shop)
57
+ shop.new_charge_attributes.merge(
58
+ return_url: DiscoApp::Engine.routes.url_helpers.activate_charge_url,
59
+ test: !Rails.configuration.x.shopify_charges_real,
60
+ )
61
+ end
62
+
63
+ # Get the appropriate Shopify API class for the given shop (either ApplicationCharge or RecurringApplicationCharge).
64
+ def self.charge_api_class(shop)
65
+ if shop.new_charge_attributes[:type] == :one_time
66
+ ShopifyAPI::ApplicationCharge
67
+ else
68
+ ShopifyAPI::RecurringApplicationCharge
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1 @@
1
+ activate_charge
@@ -0,0 +1 @@
1
+ create_charge
@@ -0,0 +1,45 @@
1
+ <% provide(:title, 'Thankyou') %>
2
+
3
+ <div class="row">
4
+ <% if @shop.charge_declined? %>
5
+ <div class="alert alert-warning">
6
+ <p>
7
+ Oops! Looks like you declined the charge.
8
+ Unfortunately, you'll have to accept the charge on the next screen in order to continue installing the application.
9
+ </p>
10
+ </div>
11
+ <% elsif @shop.charge_cancelled? %>
12
+ <div class="alert alert-warning">
13
+ <p>
14
+ Your authorized charge for this application has expired.
15
+ This could have occurred if:
16
+ </p>
17
+ <ul>
18
+ <li>You uninstalled and reinstalled the application; or</li>
19
+ <li>Your plan level has changed.</li>
20
+ </ul>
21
+ <p>
22
+ In either case, it's no problem!
23
+ Simply click okay and you'll be asked to authorize a new charge.
24
+ Don't worry - you *wont'* be billed twice.
25
+ </p>
26
+ </div>
27
+ <% else %>
28
+ <div class="alert alert-success">
29
+ <p>
30
+ Thanks for installing <%= Rails.configuration.x.shopify_app_name %>!
31
+ </p>
32
+ <p>
33
+ Before we start setting things up, we need you to authorize a charge for the application.
34
+ </p>
35
+ </div>
36
+ <% end %>
37
+ </div>
38
+
39
+ <div class="row">
40
+ <%= form_tag disco_app.create_charge_path, method: 'POST', target: '_parent' do %>
41
+ <div class="form-group">
42
+ <%= submit_tag 'Okay', class: 'form-input' %>
43
+ </div>
44
+ <% end %>
45
+ </div>
@@ -0,0 +1,7 @@
1
+ <% content_for :extra_head do %>
2
+ <meta http-equiv="refresh" content="5">
3
+ <% end %>
4
+
5
+ <p>
6
+ Installing, please wait...
7
+ </p>
@@ -0,0 +1 @@
1
+ uninstalling
@@ -0,0 +1 @@
1
+ 404 Not Found
data/config/routes.rb ADDED
@@ -0,0 +1,19 @@
1
+ DiscoApp::Engine.routes.draw do
2
+
3
+ controller :webhooks do
4
+ post 'webhooks' => :process_webhook, as: :webhooks
5
+ end
6
+
7
+ controller :charges do
8
+ get 'charges/new' => :new, as: :new_charge
9
+ post 'charges/create' => :create, as: :create_charge
10
+ get 'charges/activate' => :activate, as: :activate_charge
11
+ end
12
+
13
+ controller :install do
14
+ get 'install' => :install, as: :install
15
+ get 'installing' => :installing, as: :installing
16
+ get 'uninstalling' => :uninstalling, as: :uninstalling
17
+ end
18
+
19
+ end
@@ -0,0 +1,5 @@
1
+ class AddStatusToShops < ActiveRecord::Migration
2
+ def change
3
+ add_column :shops, :status, :integer, default: 0
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class AddMetaToShops < ActiveRecord::Migration
2
+ def change
3
+ add_column :shops, :email, :string
4
+ add_column :shops, :country_name, :string
5
+ add_column :shops, :currency, :string
6
+ add_column :shops, :money_format, :string
7
+ add_column :shops, :money_with_currency_format, :string
8
+ add_column :shops, :domain, :string
9
+ add_column :shops, :plan_name, :string
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class AddChargeStatusToShop < ActiveRecord::Migration
2
+ def change
3
+ add_column :shops, :charge_status, :integer, default: 0
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module DiscoApp
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DiscoApp
4
+ engine_name 'disco_app'
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module DiscoApp
2
+ VERSION = "0.4.2"
3
+ end