disco_app 0.12.1 → 0.12.5

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/components/shopify/buttons/_buttons.scss +1 -0
  3. data/app/clients/disco_app/api_client.rb +27 -0
  4. data/app/clients/disco_app/disco_api_error.rb +2 -0
  5. data/app/controllers/disco_app/admin/application_controller.rb +7 -0
  6. data/app/controllers/disco_app/concerns/authenticated_controller.rb +15 -0
  7. data/app/controllers/sessions_controller.rb +6 -1
  8. data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +1 -0
  9. data/app/jobs/disco_app/concerns/subscription_changed_job.rb +1 -0
  10. data/app/jobs/disco_app/send_subscription_job.rb +7 -0
  11. data/app/models/disco_app/concerns/shop.rb +10 -0
  12. data/app/models/disco_app/concerns/subscription.rb +6 -0
  13. data/app/services/disco_app/request_validation_service.rb +15 -0
  14. data/app/views/shopify_app/sessions/new.html.erb +42 -0
  15. data/config/routes.rb +1 -0
  16. data/lib/disco_app/version.rb +1 -1
  17. data/lib/generators/disco_app/templates/initializers/rollbar.rb +4 -0
  18. data/lib/generators/disco_app/templates/initializers/shopify_app.rb +0 -1
  19. data/lib/tasks/api.rake +10 -0
  20. data/test/clients/disco_app/api_client_test.rb +22 -0
  21. data/test/controllers/home_controller_test.rb +10 -1
  22. data/test/dummy/app/controllers/application_controller.rb +1 -1
  23. data/test/dummy/config/initializers/omniauth.rb +0 -2
  24. data/test/dummy/config/initializers/shopify_app.rb +0 -1
  25. data/test/fixtures/api/subscriptions/valid_request.json +40 -0
  26. data/test/fixtures/disco_app/recurring_application_charges.yml +2 -0
  27. data/test/fixtures/disco_app/shops.yml +2 -0
  28. data/test/fixtures/disco_app/subscriptions.yml +3 -0
  29. data/test/jobs/disco_app/app_installed_job_test.rb +1 -1
  30. data/test/jobs/disco_app/app_uninstalled_job_test.rb +4 -4
  31. data/test/jobs/disco_app/send_subscription_job_test.rb +24 -0
  32. data/test/test_helper.rb +1 -0
  33. metadata +21 -4
  34. data/app/views/sessions/new.html.erb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2401e1d7eeec9114ac838e4088b2e16d6ae65fffaf41c72da24b4b5c3fd83465
4
- data.tar.gz: 469f667d5e2d7b90028a849741df422dc967336f4237623853710e1f315cf096
3
+ metadata.gz: ca27fe14e53f4e34f76940c75b601e8534f3b0fc30a2fc668951793e0ab05846
4
+ data.tar.gz: def89441dbebd476d9efac4889b6854ff2e5e32d7192154b391b4d8144da26ce
5
5
  SHA512:
6
- metadata.gz: b355b0add784a4b96df01e6c98353cae3b5014f3ebef5a35515fd77ceb4dafb02e5ce30d35725450e88d1422b7dd91fddb8a9235f1c8f35c66cfcea0f82fea2d
7
- data.tar.gz: 8100d4775f5cbbc54847c5fe58de4ec8857ffcb2c0eea89e05534e8065c4d6a48b693aae9e211d03a788e8c85e2e134e01a0d062fc2745f69ee2b7ae9236d752
6
+ metadata.gz: 8bce10166c303ac988305dee9c40acb2d7ce05eff63c368144f3941784e7ef51e970c42b8350702b0e3075f65e8ed3a56ef99921d8705edea4b00c263e0cac2e
7
+ data.tar.gz: 73055ae6330d43924b75aaad82044afe09ecffb91964b7a13b2e3b2d9b748619af3a541adf0877d1820d93bed717416b62312d003219f6b2526eb9ef3dabb41d
@@ -226,6 +226,7 @@
226
226
  cursor: default;
227
227
  color: transparent;
228
228
  text-shadow: none;
229
+ background: #fff url(data:image/gif;base64,R0lGODlhEAAQAPUAAC8vL3Z2dpSUlKWlpa2trbW1tb29vcbGxs7OztbW1t7e3ufn5%2B%2Fv7%2Ff39%2F%2F%2F%2F%2F%2F%2F%2Fz09PXNzc4uLizk5OX9%2Ffzo6On19fZiYmEFBQW9vbzw8PEBAQGtrazU1NWdnZ4GBgSUlJWRkZJCQkD4%2BPj8%2FPy4uLo2NjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwH%2F%2FwAh%2BQQJBgAPACwAAAAAEAAQAAAEifDJ52QJdeq3QnoC4DSFsnFAAAIPMizaAheAEbrI0yyOEwSGRkCAKAwIO2PjoQikGktK4zAowCauzCTxkRgGYNNmajBQBWixhlEoEAyTBUIrUVwlUwFy8z0wKEYIUwkNDApBCUdLdhwDCUULjg8MCVokBQ4HBA9UfxsLBDCaIwRdGiMSCAYVDVoRACH5BAkGAAAALAAAAAAQABAAAAWRICACjjhE5agCS6QAEeQ0kLECTBTBECBBB9WCATgAYwnIrrFwOCQSRCMyQBgGEMaiMGiwTpKGF9BgcAsLVcKQGiUSI2sBfWscDIY7gUBfmQsENiIMCm0iCmkjDQgDBGMjVwdEDlxSBwllCgYNCY1eiF8JCGgDcAwJbQ1zDgcERQNEKwsEaa0zBHArMyJWJQ1tIQAh%2BQQJBgAAACwAAAAAEAAQAAAFkSAgAo5ICOWoAouwABTlMJSxAvQACNQ%2BHaoFA4CgJHiJSa%2BhcDgKg0RDYFAgCBNGMoszDAqNhsjRoEwmhRQLoRZRBCOEwXAYrlrzg4FAKLxWDAUFBDYiDE0rCn8iDQgDBGIqXnUkUAgNB1KHBg0Jj2KKLFEIflE4CWoNgg4HBAAHA3ZBBC%2BtZAQJN2QiciUNaiEAIfkECQYAAAAsAAAAABAAEAAABZMgIAKOaAzlqAJMsQCD5TQWsgINUcAWXNmjBQOgGCQIF0XlglM4HIWBomE4LBIGC%2BNQqTRYp0IjNbN0DSnAApEWXQYjhKE6XC0slwvBQNC9VkpdFSMMTit7Kg0IAwRfKicHQ1ADCA0HCQ2FBg0JjF8KLwtGCC5GLAlpDQUFDgcEAAcDdSoLBC%2BuMwQJNzMiciVjIyEAIfkECQYAAAAsAAAAABAAEAAABZEgIAKOiBjlqAJMwQAH4TRDsgINYcADUGQK1eKlGCgOg0WmgFM4HAVjw4BgKBASBiKTabAMg0IjNZNgMogUYJFeEZgmg%2BHwWi0kXImB8F7cFBkYZyNWaiIFByoNCAMEXiMOggMvUAMIDQcJDQwJGXeCXgp%2BCzUIBaQ7CQNqDQUFDjE8dUIEfjEzBDYrM3ElYyMhACH5BAkGAAAALAAAAAAQABAAAAWSICACjogY5agCTMEAB%2BE0hbICDWHABIAMC9XipRgoYowBArdwOArGhgHBWCgIjMSF0mAZBoVGakagXBJCRGp0OIxOhsNrxdiaDQRCIbhaUP4UIwwKayIFbiMNPwRdIw4UGhQ2T0oNBwkNCwQaCwMaGl0KQZ0JCBcFGgUACQVrNK0xAJ98QgRBMQ4LqTczJigsayEAIfkECQYAAAAsAAAAABAAEAAABZIgIAKOiBjlqAJMwQAH4TSFsgINYcAEgAwL1eKlGChijAECt3A4CsaGAcFYKAiNBbTBMgwKjdTsMCDYRgtEanRYmgyGw2vFIAy%2BBgKhEKQPJBIDIwwKayJtKg0%2FWCoOEhsSQU9KDWQKCQUcWhwcXApBC2WcBxuCR2s0BQ4cGwCsCTcLBEEbGw4LGzsrMyKcJWEjIQAh%2BQQJBgAAACwAAAAAEAAQAAAFkyAgAo6IGOWoAkzBAAfhNIWyAg1hwASADAvV4qUYKGKMAQK3cDgKxoYBwVgoCI0FtMEyDAqN1OzwDY4WiNQokRidDIfXqnEwwA0EQsGsYuR1IwwJaiZtIw0%2FHlwjDgMfAy9PXwoeQEcfDAcfH1wKQQYdBR8eCB49aGoMHR8OHh0AozYrBx02rg4MHgc3MyKjJWEjIQAh%2BQQJBgAAACwAAAAAEAAQAAAGmUCAEOAQIgzFoRLAKDAAB4KjUVAsAQ2CAUoAIAYL5eKpGCiijAECu3A4CuaGAcFYKAiNBbzBNAwKDUlTB39hQwsISUMJCUNHBgdPSw0HBpAGBAQDjUtNBVqOA4pCCoZCDCEgIHxKfpFEqiILIbILCSIMCQN4AFVQIAQiIQkhB0wJiqghDsIAwaaLIFvCDgwia0tTQgMiRYFDQQAh%2BQQJBgAAACwAAAAAEAAQAAAGmECAEOAQIgzFoRLAKDAAB4KjUVAsAQ2CAUoAIAYL5eKpGCiijAECu3A4CuaGAcFYKAiNBbzBNAwKDUlTB39hQwciSUMJCUMZIyMZa0sNBwYGiBmaB1dNBVqLBYpCCoZCDCKQfEp%2BB08OjyILIgQMDAkGDQkDeFCcCSOfImWNt4oNmg4DIgB%2BT0sLkgDLDgwEjUtTQn5FgUNBACH5BAkGAAAALAAAAAAQABAAAAaVQIAQ4BAiDMWhEsAoMAAHgqNRUCwBDYIBSgAgBgvlwfpVRBkDBHbhcERIA0bkwFgoCI1FYdAALEQkJAx9RA0HAwVhQwciSUMJCUMiEZSRS4YGBgdvgQdXTQVaQwpISwqKQg0DJBGEQwYDdERvcVoMDKQNCQN4XmoKcwaJA5EMCY4NIo0GXYdPS3%2BRzFMElkpTRqUNjkEAIfkECQYAAAAsAAAAABAAEAAABptAgBDgECIMxaESoCghAAeCo1FQLAGMUglKACAGC2XCeigRzozBs7FwODLmRiaTWCgI7MKgAUjAMw18RA0HAwVhQwcmSUMJCUMDJpJWS4QGBopzdFcMBQUEBkMLCIxCCohCDQaSgkMGAwcMRAMZBQwHCQ0MCgYNCQN4Xo8LAwkIh8VYCYxUBQ5RUAOySwsEYVFTBI9LU0ZIAA2MQQA7) 50% 50% no-repeat !important;
229
230
  }
230
231
 
231
232
  .btn:not(.btn-disabled).is-loading::before, .btn:not(.btn-disabled).is-loading:hover::before {
@@ -0,0 +1,27 @@
1
+ require 'rest-client'
2
+
3
+ class DiscoApp::ApiClient
4
+
5
+ SUBSCRIPTION_ENDPOINT = 'app_subscriptions.json'
6
+
7
+ def initialize(shop, url)
8
+ @shop = shop
9
+ @url = url
10
+ end
11
+
12
+ def create_app_subscription
13
+ return unless @url.present?
14
+ url = @url + SUBSCRIPTION_ENDPOINT
15
+ begin
16
+ response = RestClient::Request.execute(
17
+ method: :post,
18
+ headers: { content_type: :json },
19
+ url: url,
20
+ payload: { shop: @shop, subscription: @shop.current_subscription }.to_json
21
+ )
22
+ rescue RestClient::BadRequest, RestClient::ResourceNotFound => e
23
+ raise DiscoApiError.new(e.message)
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,2 @@
1
+ class DiscoApiError < StandardError
2
+ end
@@ -1,3 +1,10 @@
1
1
  class DiscoApp::Admin::ApplicationController < ActionController::Base
2
2
  include DiscoApp::Admin::Concerns::AuthenticatedController
3
+
4
+ private
5
+
6
+ helper_method :current_shop
7
+ def current_shop
8
+ @current_shop ||= @shop
9
+ end
3
10
  end
@@ -1,7 +1,9 @@
1
1
  module DiscoApp::Concerns::AuthenticatedController
2
2
  extend ActiveSupport::Concern
3
+ include ShopifyApp::LoginProtection
3
4
 
4
5
  included do
6
+ before_action :auto_login
5
7
  before_action :login_again_if_different_shop
6
8
  before_action :shopify_shop
7
9
  before_action :check_installed
@@ -13,6 +15,15 @@ module DiscoApp::Concerns::AuthenticatedController
13
15
 
14
16
  private
15
17
 
18
+ def auto_login
19
+ if shop_session.nil? and request_hmac_valid?
20
+ if(shop = DiscoApp::Shop.find_by_shopify_domain(sanitized_shop_name)).present?
21
+ session[:shopify] = shop.id
22
+ session[:shopify_domain] = sanitized_shop_name
23
+ end
24
+ end
25
+ end
26
+
16
27
  def shopify_shop
17
28
  if shop_session
18
29
  @shop = DiscoApp::Shop.find_by!(shopify_domain: @shop_session.url)
@@ -53,4 +64,8 @@ module DiscoApp::Concerns::AuthenticatedController
53
64
  end
54
65
  end
55
66
 
67
+ def request_hmac_valid?
68
+ DiscoApp::RequestValidationService.hmac_valid?(request.query_string, ShopifyApp.configuration.secret)
69
+ end
70
+
56
71
  end
@@ -1,5 +1,5 @@
1
1
  class SessionsController < ApplicationController
2
- include ShopifyApp::SessionsController
2
+ include ShopifyApp::SessionsConcern
3
3
 
4
4
  def referral
5
5
  cookies[DiscoApp::SOURCE_COOKIE_KEY] = params[:source] if params[:source].present?
@@ -7,6 +7,11 @@ class SessionsController < ApplicationController
7
7
  redirect_to root_path
8
8
  end
9
9
 
10
+ def failure
11
+ flash[:notice] = 'There was an issue while trying to authenticate, please retry'
12
+ redirect_to root_path
13
+ end
14
+
10
15
  protected
11
16
 
12
17
  # Override the authenticate method to allow skipping OAuth in development
@@ -14,6 +14,7 @@ module DiscoApp::Concerns::AppUninstalledJob
14
14
  #
15
15
  def perform(shop, shop_data)
16
16
  DiscoApp::ChargesService.cancel_recurring_charges(@shop)
17
+ DiscoApp::SendSubscriptionJob.perform_later(@shop)
17
18
  @shop.sessions.delete_all
18
19
  end
19
20
 
@@ -2,6 +2,7 @@ module DiscoApp::Concerns::SubscriptionChangedJob
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def perform(shop, subscription)
5
+ DiscoApp::SendSubscriptionJob.perform_later(@shop)
5
6
  end
6
7
 
7
8
  end
@@ -0,0 +1,7 @@
1
+ class DiscoApp::SendSubscriptionJob < DiscoApp::ShopJob
2
+
3
+ def perform(shop)
4
+ @shop.disco_api_client.create_app_subscription
5
+ end
6
+
7
+ end
@@ -61,6 +61,11 @@ module DiscoApp::Concerns::Shop
61
61
  "https://#{shopify_domain}/admin"
62
62
  end
63
63
 
64
+ # Convenience method to get the email of the shop's admin, to display in Rollbar.
65
+ def email
66
+ self.data['email']
67
+ end
68
+
64
69
  def installed_duration
65
70
  distance_of_time_in_words_to_now(created_at.time)
66
71
  end
@@ -81,6 +86,11 @@ module DiscoApp::Concerns::Shop
81
86
  (data['primary_locale'] || 'en').to_sym
82
87
  end
83
88
 
89
+ # Return an instance of the Disco API client.
90
+ def disco_api_client
91
+ @api_client ||= DiscoApp::ApiClient.new(self, ENV['DISCO_API_URL'])
92
+ end
93
+
84
94
  end
85
95
 
86
96
  end
@@ -47,6 +47,12 @@ module DiscoApp::Concerns::Subscription
47
47
  recurring? ? ShopifyAPI::RecurringApplicationCharge : ShopifyAPI::ApplicationCharge
48
48
  end
49
49
 
50
+ def as_json(options = {})
51
+ super.merge(
52
+ 'active_charge' => active_charge
53
+ )
54
+ end
55
+
50
56
  private
51
57
 
52
58
  # If the amount or trial period for this subscription changes, clear any
@@ -0,0 +1,15 @@
1
+ class DiscoApp::RequestValidationService
2
+
3
+ def self.hmac_valid?(query_string, secret)
4
+ query_hash = Rack::Utils.parse_query(query_string)
5
+ hmac = query_hash.delete('hmac').to_s
6
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(self.calculated_hmac(query_hash, secret), hmac)
7
+ end
8
+
9
+ # Return the calculated hmac for the given query hash and secret.
10
+ def self.calculated_hmac(query_hash, secret)
11
+ sorted_params = query_hash.collect{ |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join('&')
12
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, sorted_params)
13
+ end
14
+
15
+ end
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Install <%= DiscoApp.configuration.app_name %></title>
5
+
6
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
7
+
8
+ <%= csrf_meta_tags %>
9
+
10
+ <%= yield :extra_head %>
11
+ </head>
12
+ <body>
13
+ <%= form_tag shopify_app.login_path do %>
14
+ <div class="ui-empty-state">
15
+ <section class="ui-empty-state__section">
16
+ <div class="ui-empty-state__subsection">
17
+ <h1 class="ui-empty-state__title">Install <%= DiscoApp.configuration.app_name %></h1>
18
+ <h2 class="ui-empty-state__subtitle">Enter your Shopify store name below to get started.</h2>
19
+ </div>
20
+ <div class="ui-empty-state__subsection">
21
+ <div class="next-grid">
22
+ <div class="next-grid__cell" style="text-align: right;">
23
+ <label for="shop" class="next-input__help-text">https://</label>
24
+ </div>
25
+ <div class="next-grid__cell">
26
+ <input type="text" class="form-control" id="shop" name="shop" placeholder="your-store" autocomplete="off" autofocus="on" />
27
+ </div>
28
+ <div class="next-grid__cell" style="text-align: left;">
29
+ <label for="shop" class="next-input__help-text">.myshopify.com</label>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="ui-empty-state__subsection">
34
+ <button type="submit" class="btn btn-large btn-primary">Install</button>
35
+ </div>
36
+ </section>
37
+ </div>
38
+ <% end %>
39
+
40
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
41
+ </body>
42
+ </html>
data/config/routes.rb CHANGED
@@ -3,6 +3,7 @@ require 'sidekiq/web'
3
3
  DiscoApp::Engine.routes.draw do
4
4
 
5
5
  get 'ref', to: '/sessions#referral'
6
+ get '/auth/failure', to: '/sessions#failure'
6
7
 
7
8
  controller :webhooks do
8
9
  post 'webhooks' => :process_webhook, as: :webhooks
@@ -1,3 +1,3 @@
1
1
  module DiscoApp
2
- VERSION = '0.12.1'
2
+ VERSION = '0.12.5'
3
3
  end
@@ -10,6 +10,10 @@ Rollbar.configure do |config|
10
10
  # Enable delayed reporting (using Sidekiq)
11
11
  config.use_sidekiq
12
12
 
13
+ # Enable "Person" feature of Rollbar in the context of a "Shop"
14
+ config.person_method = 'current_shop'
15
+ config.person_username_method = 'shopify_domain'
16
+
13
17
  # Add custom handlers.
14
18
  config.before_process << proc do |options|
15
19
  if options[:exception].is_a?(ActiveResource::ClientError) and options[:exception].message.include?('Too Many Requests')
@@ -1,7 +1,6 @@
1
1
  ShopifyApp.configure do |config|
2
2
  config.api_key = ENV['SHOPIFY_APP_API_KEY']
3
3
  config.secret = ENV['SHOPIFY_APP_SECRET']
4
- config.redirect_uri = ENV['SHOPIFY_APP_REDIRECT_URI']
5
4
  config.scope = ENV['SHOPIFY_APP_SCOPE']
6
5
  config.embedded_app = true
7
6
  end
@@ -0,0 +1,10 @@
1
+ namespace :api do
2
+
3
+ desc 'Send all subscription information to the Disco API'
4
+ task send_subscriptions: :environment do
5
+ DiscoApp::Shop.find_each do |shop|
6
+ DiscoApp::SendSubscriptionJob.perform_later(shop)
7
+ end
8
+ end
9
+
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ class ApiClientTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ @shop = disco_app_shops(:widget_store)
7
+ stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").
8
+ with(body: api_fixture('subscriptions/valid_request').to_json).
9
+ to_return(status: 200, body: api_fixture('subscriptions/valid_request').to_json)
10
+ end
11
+
12
+ def teardown
13
+ @shop = nil
14
+ WebMock.reset!
15
+ end
16
+
17
+ test 'Successful disco api call render correct JSON' do
18
+ response = @shop.disco_api_client.create_app_subscription
19
+ assert_equal api_fixture('subscriptions/valid_request'), JSON.parse(response.body)
20
+ end
21
+
22
+ end
@@ -14,7 +14,7 @@ class HomeControllerTest < ActionController::TestCase
14
14
  @current_subscription = nil
15
15
  end
16
16
 
17
- test 'non-logged in user is redirected to the login page' do
17
+ test 'non-logged in user is redirected to the login page if no hmac and shop domain present' do
18
18
  log_out
19
19
  get(:index)
20
20
  assert_redirected_to ShopifyApp::Engine.routes.url_helpers.login_path
@@ -89,4 +89,13 @@ class HomeControllerTest < ActionController::TestCase
89
89
  assert_response :success
90
90
  end
91
91
 
92
+ test 'non-logged in user is logged in if valid hmac and shop domain is present in url' do
93
+ log_out
94
+ Timecop.freeze('2017-03-08 12:44:58 +1100') do
95
+ hmac = 'eb49ba93a8daf8a11a04c66129faf98de1cd40f082b0ae78e79a2dfbbefb438d'
96
+ get(:index, { hmac: hmac, shop: 'widgets-dev.myshopify.com', timestamp: Time.now.to_i })
97
+ assert_response :success
98
+ end
99
+ end
100
+
92
101
  end
@@ -1,5 +1,5 @@
1
1
  class ApplicationController < ActionController::Base
2
- include ShopifyApp::Controller
2
+ include ShopifyApp::LoginProtection
3
3
  # Prevent CSRF attacks by raising an exception.
4
4
  # For APIs, you may want to use :null_session instead.
5
5
  protect_from_forgery with: :exception
@@ -3,7 +3,5 @@ Rails.application.config.middleware.use OmniAuth::Builder do
3
3
  ShopifyApp.configuration.api_key,
4
4
  ShopifyApp.configuration.secret,
5
5
 
6
- :redirect_uri => ShopifyApp.configuration.redirect_uri,
7
-
8
6
  :scope => ShopifyApp.configuration.scope
9
7
  end
@@ -1,7 +1,6 @@
1
1
  ShopifyApp.configure do |config|
2
2
  config.api_key = ENV['SHOPIFY_APP_API_KEY']
3
3
  config.secret = ENV['SHOPIFY_APP_SECRET']
4
- config.redirect_uri = ENV['SHOPIFY_APP_REDIRECT_URI']
5
4
  config.scope = ENV['SHOPIFY_APP_SCOPE']
6
5
  config.embedded_app = true
7
6
  end
@@ -0,0 +1,40 @@
1
+ {
2
+ "shop": {
3
+ "id": 605094243,
4
+ "shopify_domain": "widgets.myshopify.com",
5
+ "shopify_token": "0",
6
+ "created_at": "2017-03-07T06:06:25.000Z",
7
+ "updated_at": "2017-03-07T06:06:25.000Z",
8
+ "status": "never_installed",
9
+ "domain": null,
10
+ "plan_name": null,
11
+ "name": null,
12
+ "data": {"timezone": "(GMT+10:00) Melbourne", "country_name": "Australia"}
13
+ },
14
+ "subscription": {
15
+ "id": 304261385,
16
+ "shop_id": 605094243,
17
+ "plan_id": 276395349,
18
+ "status": "active",
19
+ "subscription_type": "recurring",
20
+ "created_at": "2017-03-07T06:06:25.000Z",
21
+ "updated_at": "2017-03-07T06:06:25.000Z",
22
+ "trial_start_at": null,
23
+ "trial_end_at": null,
24
+ "cancelled_at": null,
25
+ "amount": 999,
26
+ "plan_code_id": null,
27
+ "source": null,
28
+ "trial_period_days": 14,
29
+ "active_charge": {
30
+ "id":332186283,
31
+ "shop_id":605094243,
32
+ "subscription_id":304261385,
33
+ "status":"active",
34
+ "created_at":"2017-03-07T06:06:25.000Z",
35
+ "updated_at":"2017-03-07T06:06:25.000Z",
36
+ "shopify_id":null,
37
+ "confirmation_url":null
38
+ }
39
+ }
40
+ }
@@ -2,6 +2,8 @@ current_widget_store_subscription_recurring_charge:
2
2
  shop: widget_store
3
3
  subscription: current_widget_store_subscription
4
4
  status: <%= DiscoApp::RecurringApplicationCharge.statuses[:active] %>
5
+ created_at: "2017-03-07T06:06:25.000Z"
6
+ updated_at: "2017-03-07T06:06:25.000Z"
5
7
 
6
8
  new_widget_store_subscription_recurring_charge:
7
9
  shop: widget_store
@@ -1,6 +1,8 @@
1
1
  widget_store:
2
2
  shopify_domain: widgets.myshopify.com
3
3
  shopify_token: 00000000000000000000000000000000
4
+ created_at: "2017-03-07T06:06:25.000Z"
5
+ updated_at: "2017-03-07T06:06:25.000Z"
4
6
  data: '{ "country_name": "Australia", "timezone": "(GMT+10:00) Melbourne" }'
5
7
 
6
8
  widget_store_dev:
@@ -13,6 +13,9 @@ current_widget_store_subscription:
13
13
  trial_period_days: 14
14
14
  status: <%= DiscoApp::Subscription.statuses[:active] %>
15
15
  subscription_type: 0
16
+ created_at: "2017-03-07T06:06:25.000Z"
17
+ updated_at: "2017-03-07T06:06:25.000Z"
18
+
16
19
 
17
20
  current_widget_store_dev_subscription:
18
21
  shop: widget_store_dev
@@ -11,11 +11,11 @@ class DiscoApp::AppInstalledJobTest < ActionController::TestCase
11
11
  stub_request(:get, "#{@shop.admin_url}/shop.json").to_return(status: 200, body: api_fixture('widget_store/shop').to_json)
12
12
  stub_request(:get, "#{@shop.admin_url}/carrier_services.json").to_return(status: 200, body: api_fixture('widget_store/carrier_services').to_json)
13
13
  stub_request(:post, "#{@shop.admin_url}/carrier_services.json").to_return(status: 200)
14
+ stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").to_return(status: 200)
14
15
  end
15
16
 
16
17
  def teardown
17
18
  @shop = nil
18
-
19
19
  WebMock.reset!
20
20
  end
21
21
 
@@ -5,7 +5,7 @@ class DiscoApp::AppUninstalledJobTest < ActionController::TestCase
5
5
 
6
6
  def setup
7
7
  @shop = disco_app_shops(:widget_store)
8
-
8
+ stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").to_return(status: 200)
9
9
  perform_enqueued_jobs do
10
10
  DiscoApp::AppUninstalledJob.perform_later(@shop, {})
11
11
  end
@@ -13,18 +13,18 @@ class DiscoApp::AppUninstalledJobTest < ActionController::TestCase
13
13
 
14
14
  def teardown
15
15
  @shop = nil
16
+ WebMock.reset!
16
17
  end
17
18
 
18
19
  test 'app uninstalled job changes shop status' do
19
- assert_performed_jobs 1
20
+ assert_performed_jobs 2
20
21
  @shop.reload
21
22
  assert @shop.uninstalled?
22
23
  end
23
24
 
24
25
  test 'app uninstalled job can be extended using concerns' do
25
- assert_performed_jobs 1
26
+ assert_performed_jobs 2
26
27
  @shop.reload
27
28
  assert_equal 'Nowhere', @shop.data['country_name'] # Assert extended method called.
28
29
  end
29
-
30
30
  end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+
3
+ class DiscoApp::SendSubscriptionJobTest < ActionController::TestCase
4
+ include ActiveJob::TestHelper
5
+
6
+ def setup
7
+ @shop = disco_app_shops(:widget_store)
8
+ stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").to_return(status: 200)
9
+ end
10
+
11
+ def teardown
12
+ @shop = nil
13
+
14
+ WebMock.reset!
15
+ end
16
+
17
+ test 'subscription job correctly sends request to API' do
18
+ perform_enqueued_jobs do
19
+ DiscoApp::SendSubscriptionJob.perform_later(@shop)
20
+ end
21
+ assert_requested(:post, "https://api.discolabs.com/v1/app_subscriptions.json", times: 1)
22
+ end
23
+
24
+ end
data/test/test_helper.rb CHANGED
@@ -10,6 +10,7 @@ ENV['SHOPIFY_APP_SECRET'] = 'b607d1f8b992dccb017f9315f07af9c4'
10
10
  ENV['SHOPIFY_APP_REDIRECT_URI'] = 'https://test.example.com/shopify/auth/callback'
11
11
  ENV['SHOPIFY_APP_SCOPE'] = 'read_products'
12
12
  ENV['SHOPIFY_CHARGES_REAL'] = 'false'
13
+ ENV['DISCO_API_URL'] = 'https://api.discolabs.com/v1/'
13
14
 
14
15
  require File.expand_path("../../test/dummy/config/environment.rb", __FILE__)
15
16
  ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: disco_app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.12.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gavin Ballard
@@ -100,14 +100,20 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '6.4'
103
+ version: '7.2'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 7.2.3
104
107
  type: :runtime
105
108
  prerelease: false
106
109
  version_requirements: !ruby/object:Gem::Requirement
107
110
  requirements:
108
111
  - - "~>"
109
112
  - !ruby/object:Gem::Version
110
- version: '6.4'
113
+ version: '7.2'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 7.2.3
111
117
  - !ruby/object:Gem::Dependency
112
118
  name: puma
113
119
  requirement: !ruby/object:Gem::Requirement
@@ -526,6 +532,8 @@ files:
526
532
  - app/assets/stylesheets/disco_app/ui-kit/_ui-layout.scss
527
533
  - app/assets/stylesheets/disco_app/ui-kit/_ui-tabs.scss
528
534
  - app/assets/stylesheets/disco_app/ui-kit/_ui-type.scss
535
+ - app/clients/disco_app/api_client.rb
536
+ - app/clients/disco_app/disco_api_error.rb
529
537
  - app/controllers/disco_app/admin/app_settings_controller.rb
530
538
  - app/controllers/disco_app/admin/application_controller.rb
531
539
  - app/controllers/disco_app/admin/concerns/app_settings_controller.rb
@@ -558,6 +566,7 @@ files:
558
566
  - app/jobs/disco_app/concerns/synchronise_resources_job.rb
559
567
  - app/jobs/disco_app/concerns/synchronise_webhooks_job.rb
560
568
  - app/jobs/disco_app/render_asset_group_job.rb
569
+ - app/jobs/disco_app/send_subscription_job.rb
561
570
  - app/jobs/disco_app/shop_job.rb
562
571
  - app/jobs/disco_app/shop_update_job.rb
563
572
  - app/jobs/disco_app/subscription_changed_job.rb
@@ -587,6 +596,7 @@ files:
587
596
  - app/services/disco_app/carrier_request_service.rb
588
597
  - app/services/disco_app/charges_service.rb
589
598
  - app/services/disco_app/proxy_service.rb
599
+ - app/services/disco_app/request_validation_service.rb
590
600
  - app/services/disco_app/subscription_service.rb
591
601
  - app/services/disco_app/webhook_service.rb
592
602
  - app/views/disco_app/admin/app_settings/edit.html.erb
@@ -613,7 +623,7 @@ files:
613
623
  - app/views/layouts/application.html.erb
614
624
  - app/views/layouts/embedded_app.html.erb
615
625
  - app/views/layouts/embedded_app_modal.html.erb
616
- - app/views/sessions/new.html.erb
626
+ - app/views/shopify_app/sessions/new.html.erb
617
627
  - config/routes.rb
618
628
  - db/migrate/20150525000000_create_shops_if_not_existent.rb
619
629
  - lib/disco_app.rb
@@ -641,12 +651,14 @@ files:
641
651
  - lib/generators/disco_app/templates/root/CHECKS
642
652
  - lib/generators/disco_app/templates/root/Procfile
643
653
  - lib/generators/disco_app/templates/views/home/index.html.erb
654
+ - lib/tasks/api.rake
644
655
  - lib/tasks/carrier_service.rake
645
656
  - lib/tasks/database.rake
646
657
  - lib/tasks/sessions.rake
647
658
  - lib/tasks/shops.rake
648
659
  - lib/tasks/start.rake
649
660
  - lib/tasks/webhooks.rake
661
+ - test/clients/disco_app/api_client_test.rb
650
662
  - test/controllers/disco_app/admin/shops_controller_test.rb
651
663
  - test/controllers/disco_app/charges_controller_test.rb
652
664
  - test/controllers/disco_app/install_controller_test.rb
@@ -718,6 +730,7 @@ files:
718
730
  - test/dummy/public/422.html
719
731
  - test/dummy/public/500.html
720
732
  - test/dummy/public/favicon.ico
733
+ - test/fixtures/api/subscriptions/valid_request.json
721
734
  - test/fixtures/api/widget_store/assets/create_script_tag_js_request.json
722
735
  - test/fixtures/api/widget_store/assets/create_script_tag_js_response.json
723
736
  - test/fixtures/api/widget_store/assets/create_script_tag_request.json
@@ -781,6 +794,7 @@ files:
781
794
  - test/integration/synchronises_test.rb
782
795
  - test/jobs/disco_app/app_installed_job_test.rb
783
796
  - test/jobs/disco_app/app_uninstalled_job_test.rb
797
+ - test/jobs/disco_app/send_subscription_job_test.rb
784
798
  - test/jobs/disco_app/synchronise_carrier_service_job_test.rb
785
799
  - test/jobs/disco_app/synchronise_webhooks_job_test.rb
786
800
  - test/models/disco_app/can_be_liquified_test.rb
@@ -884,6 +898,7 @@ test_files:
884
898
  - test/dummy/config/database.gitlab-ci.yml
885
899
  - test/dummy/config/application.rb
886
900
  - test/fixtures/liquid/model.liquid
901
+ - test/fixtures/api/subscriptions/valid_request.json
887
902
  - test/fixtures/api/widget_store/carrier_services.json
888
903
  - test/fixtures/api/widget_store/assets/create_test_js_response.json
889
904
  - test/fixtures/api/widget_store/assets/create_widget_js_response.json
@@ -949,6 +964,7 @@ test_files:
949
964
  - test/disco_app_test.rb
950
965
  - test/jobs/disco_app/app_uninstalled_job_test.rb
951
966
  - test/jobs/disco_app/synchronise_carrier_service_job_test.rb
967
+ - test/jobs/disco_app/send_subscription_job_test.rb
952
968
  - test/jobs/disco_app/synchronise_webhooks_job_test.rb
953
969
  - test/jobs/disco_app/app_installed_job_test.rb
954
970
  - test/controllers/disco_app/install_controller_test.rb
@@ -959,6 +975,7 @@ test_files:
959
975
  - test/controllers/proxy_controller_test.rb
960
976
  - test/controllers/home_controller_test.rb
961
977
  - test/integration/synchronises_test.rb
978
+ - test/clients/disco_app/api_client_test.rb
962
979
  - test/models/disco_app/plan_test.rb
963
980
  - test/models/disco_app/has_metafields_test.rb
964
981
  - test/models/disco_app/shop_test.rb
@@ -1,28 +0,0 @@
1
- <% provide(:title, "Install #{DiscoApp.configuration.app_name}") %>
2
-
3
- <%= form_tag shopify_app.login_path do %>
4
- <div class="ui-empty-state">
5
- <section class="ui-empty-state__section">
6
- <div class="ui-empty-state__subsection">
7
- <h1 class="ui-empty-state__title">Install <%= DiscoApp.configuration.app_name %></h1>
8
- <h2 class="ui-empty-state__subtitle">Enter your Shopify store name below to get started.</h2>
9
- </div>
10
- <div class="ui-empty-state__subsection">
11
- <div class="next-grid">
12
- <div class="next-grid__cell" style="text-align: right;">
13
- <label for="shop" class="next-input__help-text">https://</label>
14
- </div>
15
- <div class="next-grid__cell">
16
- <input type="text" class="form-control" id="shop" name="shop" placeholder="your-store" autocomplete="off" autofocus="on" />
17
- </div>
18
- <div class="next-grid__cell" style="text-align: left;">
19
- <label for="shop" class="next-input__help-text">.myshopify.com</label>
20
- </div>
21
- </div>
22
- </div>
23
- <div class="ui-empty-state__subsection">
24
- <button type="submit" class="btn btn-large btn-primary">Install</button>
25
- </div>
26
- </section>
27
- </div>
28
- <% end %>