disco_app 0.8.7 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/disco_app/admin/concerns/plans_controller.rb +51 -0
  3. data/app/controllers/disco_app/admin/plans_controller.rb +3 -0
  4. data/app/controllers/disco_app/charges_controller.rb +37 -20
  5. data/app/controllers/disco_app/concerns/app_proxy_controller.rb +1 -1
  6. data/app/controllers/disco_app/concerns/authenticated_controller.rb +26 -12
  7. data/app/controllers/disco_app/concerns/carrier_request_controller.rb +1 -1
  8. data/app/controllers/disco_app/install_controller.rb +20 -19
  9. data/app/controllers/disco_app/subscriptions_controller.rb +40 -0
  10. data/app/controllers/disco_app/webhooks_controller.rb +1 -1
  11. data/app/controllers/sessions_controller.rb +22 -0
  12. data/app/jobs/disco_app/concerns/app_installed_job.rb +21 -4
  13. data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +2 -5
  14. data/app/jobs/disco_app/concerns/subscription_changed_job.rb +7 -0
  15. data/app/jobs/disco_app/subscription_changed_job.rb +3 -0
  16. data/app/models/disco_app/application_charge.rb +18 -0
  17. data/app/models/disco_app/concerns/plan.rb +13 -1
  18. data/app/models/disco_app/concerns/plan_code.rb +15 -0
  19. data/app/models/disco_app/concerns/shop.rb +10 -9
  20. data/app/models/disco_app/concerns/subscription.rb +36 -2
  21. data/app/models/disco_app/plan_code.rb +3 -0
  22. data/app/models/disco_app/recurring_application_charge.rb +18 -0
  23. data/app/services/disco_app/charges_service.rb +66 -58
  24. data/app/services/disco_app/subscription_service.rb +27 -15
  25. data/app/views/disco_app/admin/plans/_form.html.erb +27 -0
  26. data/app/views/disco_app/admin/plans/edit.html.erb +7 -0
  27. data/app/views/disco_app/admin/plans/index.html.erb +32 -0
  28. data/app/views/disco_app/admin/plans/new.html.erb +7 -0
  29. data/app/views/disco_app/charges/new.html.erb +9 -42
  30. data/app/views/disco_app/subscriptions/new.html.erb +25 -0
  31. data/app/views/layouts/admin/_navbar.html.erb +1 -0
  32. data/config/routes.rb +8 -5
  33. data/db/migrate/20160301223215_update_plans.rb +22 -0
  34. data/db/migrate/20160301224558_update_subscriptions.rb +13 -0
  35. data/db/migrate/20160302104816_create_disco_app_recurring_application_charges.rb +14 -0
  36. data/db/migrate/20160302105259_create_disco_app_application_charges.rb +14 -0
  37. data/db/migrate/20160302134728_drop_charge_status_from_shops.rb +5 -0
  38. data/db/migrate/20160302142941_add_shopify_attributes_to_charges.rb +8 -0
  39. data/db/migrate/20160331093148_create_disco_app_plan_codes.rb +14 -0
  40. data/db/migrate/20160401044420_add_status_to_plan_codes.rb +5 -0
  41. data/db/migrate/20160401045551_add_amount_and_plan_code_to_disco_app_subscriptions.rb +7 -0
  42. data/lib/disco_app/configuration.rb +6 -0
  43. data/lib/disco_app/version.rb +1 -1
  44. data/lib/generators/disco_app/reactify/reactify_generator.rb +1 -1
  45. data/lib/generators/disco_app/templates/initializers/disco_app.rb +9 -4
  46. data/test/controllers/disco_app/charges_controller_test.rb +91 -0
  47. data/test/controllers/disco_app/subscriptions_controller_test.rb +73 -0
  48. data/test/controllers/home_controller_test.rb +37 -6
  49. data/test/dummy/app/jobs/disco_app/app_installed_job.rb +16 -0
  50. data/test/dummy/config/initializers/disco_app.rb +9 -4
  51. data/test/dummy/db/schema.rb +54 -14
  52. data/test/fixtures/api/widget_store/charges/activate_application_charge_request.json +16 -0
  53. data/test/fixtures/api/widget_store/charges/activate_application_charge_response.json +1 -0
  54. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_request.json +20 -0
  55. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_response.json +1 -0
  56. data/test/fixtures/api/widget_store/charges/create_application_charge_request.json +9 -0
  57. data/test/fixtures/api/widget_store/charges/create_application_charge_response.json +16 -0
  58. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +9 -0
  59. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_response.json +20 -0
  60. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +9 -0
  61. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_response.json +20 -0
  62. data/test/fixtures/api/widget_store/charges/get_accepted_application_charge_response.json +16 -0
  63. data/test/fixtures/api/widget_store/charges/get_accepted_recurring_application_charge_response.json +20 -0
  64. data/test/fixtures/api/widget_store/charges/get_declined_application_charge_response.json +16 -0
  65. data/test/fixtures/api/widget_store/charges/get_declined_recurring_application_charge_response.json +20 -0
  66. data/test/fixtures/api/widget_store/charges/get_pending_application_charge_response.json +16 -0
  67. data/test/fixtures/api/widget_store/charges/get_pending_recurring_application_charge_response.json +20 -0
  68. data/test/fixtures/disco_app/application_charges.yml +11 -0
  69. data/test/fixtures/disco_app/plan_codes.yml +6 -0
  70. data/test/fixtures/disco_app/plans.yml +23 -18
  71. data/test/fixtures/disco_app/recurring_application_charges.yml +11 -0
  72. data/test/fixtures/disco_app/subscriptions.yml +12 -17
  73. data/test/jobs/disco_app/app_installed_job_test.rb +17 -5
  74. data/test/jobs/disco_app/app_uninstalled_job_test.rb +0 -2
  75. data/test/models/disco_app/shop_test.rb +3 -2
  76. data/test/services/disco_app/charges_service_test.rb +104 -0
  77. data/test/services/disco_app/subscription_service_test.rb +39 -8
  78. data/test/support/test_file_fixtures.rb +1 -1
  79. data/test/support/test_shopify_api.rb +16 -0
  80. data/test/test_helper.rb +1 -0
  81. metadata +73 -3
  82. data/test/models/disco_app/subscription_test.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d435f0f5ba6860fac53344b430a0177ba110b35d118d11a43c597ed460ec5fb
4
- data.tar.gz: 1df37961682c32e2c594cb2852090f2031b8b1bce6bf1380bd85c371d51537ba
3
+ metadata.gz: 6d121a39b30f43fccab67728b99a8bc4e4aed5ef0fea0aaaba6c72f35b2c30b3
4
+ data.tar.gz: 8a6dd1711f023374e251243c661c4eec548aa1613ab39175c4e5b07975400579
5
5
  SHA512:
6
- metadata.gz: a7651d300a93a6eed409cb120aa8934c9a8970457d4b2c8ee933db2b6897f8b842b29ba0c45545fa30cc9ef33404473964058ddb3fe2a5352f3d6094d3961505
7
- data.tar.gz: 44cf0e056f7c302a8e3202f5c75a4049e4461cc9802232d4bccc37cf982eda987c85ccef3b5a7e2c36d4fb5bca10b43040072c2799c182e7b27c254c5fcea807
6
+ metadata.gz: 8d9ad557bccac5b7d620b2e6ffab730588394c8ab8570836c376dbb72080da66e79ddec488efcb9f638c44a5a57d793b13022fd96fe50c29d0fe5700d4fa9e04
7
+ data.tar.gz: bfe171a5488905b6704eb84e39f5f1a4c1a6603d337b18df96d8e8923c4706b889595cf6532ea83c1f28fe77f6f91c0bd476b9e00018f7fb95b5aba43ab4007b
@@ -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,3 @@
1
+ class DiscoApp::Admin::PlansController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::PlansController
3
+ end
@@ -1,30 +1,47 @@
1
- module DiscoApp
2
- class ChargesController < ApplicationController
3
- include DiscoApp::Concerns::AuthenticatedController
1
+ class DiscoApp::ChargesController < ApplicationController
2
+ include DiscoApp::Concerns::AuthenticatedController
4
3
 
5
- skip_before_action :verify_status, only: [:create, :activate]
4
+ skip_before_action :check_active_charge
5
+ before_action :find_subscription
6
6
 
7
- # Display a "pre-charge" page, giving the opportunity to explain why a charge needs to be made.
8
- def new
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
9
20
  end
21
+ end
10
22
 
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
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
17
35
  end
36
+ end
37
+
38
+ private
18
39
 
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
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
24
44
  end
25
- DiscoApp::ChargesService.activate(@shop, shopify_charge)
26
- redirect_to main_app.root_url
27
45
  end
28
46
 
29
- end
30
47
  end
@@ -20,7 +20,7 @@ module DiscoApp::Concerns::AppProxyController
20
20
  end
21
21
 
22
22
  def proxy_signature_is_valid?
23
- return true if DiscoApp.configuration.skip_proxy_verification?
23
+ return true if Rails.env.development? and DiscoApp.configuration.skip_proxy_verification?
24
24
  DiscoApp::ProxyService.proxy_signature_is_valid?(request.query_string, ShopifyApp.configuration.secret)
25
25
  end
26
26
 
@@ -4,7 +4,9 @@ module DiscoApp::Concerns::AuthenticatedController
4
4
  included do
5
5
  before_action :login_again_if_different_shop
6
6
  before_action :shopify_shop
7
- before_action :verify_status
7
+ before_action :check_installed
8
+ before_action :check_current_subscription
9
+ before_action :check_active_charge
8
10
  around_filter :shopify_session
9
11
  layout 'embedded_app'
10
12
  end
@@ -19,17 +21,29 @@ module DiscoApp::Concerns::AuthenticatedController
19
21
  end
20
22
  end
21
23
 
22
- def verify_status
23
- if not (@shop.charge_active? or @shop.charge_waived?)
24
- redirect_if_not_current_path(disco_app.new_charge_path)
25
- elsif @shop.charge_accepted?
26
- redirect_if_not_current_path(disco_app.activate_charge_path)
27
- elsif @shop.never_installed? or @shop.uninstalled?
28
- redirect_if_not_current_path(disco_app.install_path)
29
- elsif @shop.awaiting_install? or @shop.installing?
30
- redirect_if_not_current_path(disco_app.installing_path)
31
- elsif @shop.awaiting_uninstall? or @shop.uninstalling?
32
- redirect_if_not_current_path(disco_app.uninstalling_path)
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)
33
47
  end
34
48
  end
35
49
 
@@ -14,7 +14,7 @@ module DiscoApp::Concerns::CarrierRequestController
14
14
  end
15
15
 
16
16
  def carrier_request_signature_is_valid?
17
- return true if DiscoApp.configuration.skip_carrier_request_verification?
17
+ return true if Rails.env.development? and DiscoApp.configuration.skip_carrier_request_verification?
18
18
  DiscoApp::CarrierRequestService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
19
19
  end
20
20
 
@@ -1,26 +1,27 @@
1
- module DiscoApp
2
- class InstallController < ApplicationController
3
- include DiscoApp::Concerns::AuthenticatedController
1
+ class DiscoApp::InstallController < ApplicationController
2
+ include DiscoApp::Concerns::AuthenticatedController
4
3
 
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
4
+ skip_before_action :check_current_subscription
5
+ skip_before_action :check_active_charge
10
6
 
11
- # Display an "installing" page.
12
- def installing
13
- if @shop.installed?
14
- redirect_to main_app.root_path
15
- end
16
- end
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
17
12
 
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
13
+ # Display an "installing" page.
14
+ def installing
15
+ if @shop.installed?
16
+ redirect_to main_app.root_path
23
17
  end
18
+ end
24
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
25
  end
26
+
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
@@ -38,7 +38,7 @@ module DiscoApp
38
38
  end
39
39
 
40
40
  def webhook_is_valid?
41
- return true if DiscoApp.configuration.skip_webhook_verification?
41
+ return true if Rails.env.development? and DiscoApp.configuration.skip_webhook_verification?
42
42
  DiscoApp::WebhookService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
43
43
  end
44
44
 
@@ -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
@@ -12,11 +12,28 @@ module DiscoApp::Concerns::AppInstalledJob
12
12
  # - Synchronise webhooks.
13
13
  # - Synchronise carrier service, if required.
14
14
  # - Perform initial update of shop information.
15
+ # - Subscribe to default plan, if any exists.
15
16
  #
16
- def perform(domain)
17
- DiscoApp::SynchroniseWebhooksJob.perform_now(domain)
18
- DiscoApp::SynchroniseCarrierServiceJob.perform_now(domain)
19
- DiscoApp::ShopUpdateJob.perform_now(domain)
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
20
37
  end
21
38
 
22
39
  end
@@ -9,14 +9,11 @@ module DiscoApp::Concerns::AppUninstalledJob
9
9
 
10
10
  # Perform application uninstallation.
11
11
  #
12
- # - Mark charge status as "cancelled" unless charges have been waived.
12
+ # - Mark any recurring application charges as cancelled.
13
13
  # - Remove any stored sessions for the shop.
14
14
  #
15
15
  def perform(domain, shop_data)
16
- unless @shop.charge_waived?
17
- @shop.charge_cancelled!
18
- end
19
-
16
+ DiscoApp::ChargesService.cancel_recurring_charges(@shop)
20
17
  @shop.sessions.delete_all
21
18
  end
22
19
 
@@ -0,0 +1,7 @@
1
+ module DiscoApp::Concerns::SubscriptionChangedJob
2
+ extend ActiveSupport::Concern
3
+
4
+ def perform(shopify_domain, subscription)
5
+ end
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::SubscriptionChangedJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::SubscriptionChangedJob
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
@@ -5,10 +5,22 @@ module DiscoApp::Concerns::Plan
5
5
 
6
6
  has_many :subscriptions
7
7
  has_many :shops, through: :subscriptions
8
+ has_many :plan_codes
8
9
 
9
- enum status: [:available, :unavailable, :hidden]
10
+ accepts_nested_attributes_for :plan_codes
11
+
12
+ enum status: [:available, :unavailable]
13
+ enum plan_type: [:recurring, :one_time]
14
+ enum interval: [:month, :year]
10
15
 
11
16
  scope :available, -> { where status: statuses[:available] }
12
17
 
18
+ validates_presence_of :name
19
+
13
20
  end
21
+
22
+ def has_trial?
23
+ trial_period_days.present? and trial_period_days > 0
24
+ end
25
+
14
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
@@ -15,9 +15,6 @@ module DiscoApp::Concerns::Shop
15
15
  # Define possible installation statuses as an enum.
16
16
  enum status: [:never_installed, :awaiting_install, :installing, :installed, :awaiting_uninstall, :uninstalling, :uninstalled]
17
17
 
18
- # Define possible charge statuses as an enum.
19
- enum charge_status: [:charge_none, :charge_pending, :charge_accepted, :charge_declined, :charge_active, :charge_cancelled, :charge_waived]
20
-
21
18
  # Define some useful scopes.
22
19
  scope :status, -> (status) { where status: status }
23
20
  scope :installed, -> { where status: statuses[:installed] }
@@ -39,15 +36,19 @@ module DiscoApp::Concerns::Shop
39
36
  }
40
37
  end
41
38
 
42
- # Update this Shop's charge_status attribute based on the given Shopify charge object.
43
- def update_charge_status(shopify_charge)
44
- status_update_method_name = "charge_#{shopify_charge.status}!"
45
- self.public_send(status_update_method_name) if self.respond_to? status_update_method_name
39
+ # Convenience method to check if this shop has a current subscription.
40
+ def current_subscription?
41
+ current_subscription.present?
46
42
  end
47
43
 
48
- # Convenience method to get the currently active subscription for this Shop.
44
+ # Convenience method to get the current subscription for this shop, if any.
49
45
  def current_subscription
50
- subscriptions.active.first
46
+ subscriptions.current.first
47
+ end
48
+
49
+ # Convenience method to get the current plan for this shop, if any.
50
+ def current_plan
51
+ current_subscription&.plan
51
52
  end
52
53
 
53
54
  # Return the absolute URL to the shop's storefront.
@@ -5,10 +5,44 @@ module DiscoApp::Concerns::Subscription
5
5
 
6
6
  belongs_to :shop
7
7
  belongs_to :plan
8
+ belongs_to :plan_code
8
9
 
9
- enum status: [:active, :replaced, :cancelled]
10
+ has_many :one_time_charges, class_name: 'DiscoApp::ApplicationCharge', dependent: :destroy
11
+ has_many :recurring_charges, class_name: 'DiscoApp::RecurringApplicationCharge', dependent: :destroy
10
12
 
11
- scope :active, -> { where status: statuses[:active] }
13
+ enum status: [:trial, :active, :cancelled]
14
+ enum subscription_type: [:recurring, :one_time]
12
15
 
16
+ scope :current, -> { where status: [statuses[:trial], statuses[:active]] }
17
+
18
+ end
19
+
20
+ # Only require an active charge if the amount to be charged is > 0.
21
+ def requires_active_charge?
22
+ amount > 0
23
+ end
24
+
25
+ # Convenience method to check if this subscription has an active charge.
26
+ def active_charge?
27
+ active_charge.present?
28
+ end
29
+
30
+ # Convenience method to get the active charge for this subscription.
31
+ def active_charge
32
+ charges.active.first
13
33
  end
34
+
35
+ # Return the appropriate set of charges for this subscription's type.
36
+ def charges
37
+ recurring? ? recurring_charges : one_time_charges
38
+ end
39
+
40
+ def charge_class
41
+ recurring? ? DiscoApp::RecurringApplicationCharge : DiscoApp::ApplicationCharge
42
+ end
43
+
44
+ def shopify_charge_class
45
+ recurring? ? ShopifyAPI::RecurringApplicationCharge : ShopifyAPI::ApplicationCharge
46
+ end
47
+
14
48
  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