disco_app 0.12.1 → 0.12.5

Sign up to get free protection for your applications and to get access to all the features.
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 %>