disco_app 0.12.7.pre.puma.pre.3 → 0.13.6.pre.puma.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/components/disco_app/forms/model-form.es6.jsx +2 -7
- data/app/assets/javascripts/disco_app/components/custom/shop_row.js.jsx +2 -1
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_form.es6.jsx +16 -44
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-checkbox.es6.jsx +10 -5
- data/app/controllers/disco_app/admin/application_controller.rb +2 -1
- data/app/controllers/disco_app/admin/concerns/sources_controller.rb +51 -0
- data/app/controllers/disco_app/admin/sources_controller.rb +3 -0
- data/app/controllers/disco_app/concerns/user_authenticated_controller.rb +17 -0
- data/app/controllers/disco_app/user_sessions_controller.rb +57 -0
- data/app/jobs/disco_app/concerns/app_installed_job.rb +1 -1
- data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +1 -1
- data/app/jobs/disco_app/concerns/render_asset_group_job.rb +1 -1
- data/app/jobs/disco_app/concerns/shop_update_job.rb +1 -1
- data/app/jobs/disco_app/concerns/subscription_changed_job.rb +1 -1
- data/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +1 -1
- data/app/jobs/disco_app/concerns/synchronise_resources_job.rb +1 -1
- data/app/jobs/disco_app/concerns/synchronise_users_job.rb +15 -0
- data/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +1 -1
- data/app/jobs/disco_app/send_subscription_job.rb +1 -1
- data/app/jobs/disco_app/shop_job.rb +10 -3
- data/app/jobs/disco_app/synchronise_users_job.rb +3 -0
- data/app/models/disco_app/concerns/shop.rb +13 -4
- data/app/models/disco_app/concerns/source.rb +14 -0
- data/app/models/disco_app/concerns/subscription.rb +1 -1
- data/app/models/disco_app/concerns/synchronises.rb +9 -0
- data/app/models/disco_app/concerns/taggable.rb +7 -3
- data/app/models/disco_app/concerns/user.rb +20 -0
- data/app/models/disco_app/source.rb +3 -0
- data/app/models/disco_app/user.rb +3 -0
- data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +4 -4
- data/app/services/disco_app/subscription_service.rb +8 -2
- data/app/views/disco_app/admin/sources/_form.html.erb +34 -0
- data/app/views/disco_app/admin/sources/edit.html.erb +7 -0
- data/app/views/disco_app/admin/sources/index.html.erb +32 -0
- data/app/views/disco_app/admin/sources/new.html.erb +7 -0
- data/app/views/disco_app/user_sessions/new.html.erb +12 -0
- data/app/views/layouts/admin/_nav_items.erb +7 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20170315062548_create_disco_app_sources.rb +10 -0
- data/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb +14 -0
- data/db/migrate/20170327214540_create_disco_app_users.rb +13 -0
- data/db/migrate/20170606160751_fix_disco_app_users_index.rb +6 -0
- data/lib/disco_app/version.rb +1 -1
- data/lib/generators/disco_app/disco_app_generator.rb +1 -1
- data/lib/generators/disco_app/templates/config/database.yml.tt +1 -0
- data/lib/tasks/users.rake +10 -0
- data/test/dummy/app/jobs/carts_update_job.rb +1 -1
- data/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +2 -2
- data/test/dummy/app/jobs/products_create_job.rb +1 -1
- data/test/dummy/app/jobs/products_delete_job.rb +1 -1
- data/test/dummy/app/jobs/products_update_job.rb +1 -1
- data/test/dummy/app/models/cart.rb +3 -3
- data/test/dummy/app/models/disco_app/shop.rb +1 -1
- data/test/dummy/config/database.yml +1 -0
- data/test/dummy/db/schema.rb +23 -2
- data/test/fixtures/api/subscriptions/valid_request.json +1 -1
- data/test/fixtures/api/widget_store/users.json +42 -0
- data/test/fixtures/disco_app/sources.yml +3 -0
- data/test/integration/synchronises_test.rb +3 -3
- data/test/jobs/disco_app/app_installed_job_test.rb +3 -3
- data/test/jobs/disco_app/app_uninstalled_job_test.rb +1 -1
- data/test/jobs/disco_app/synchronise_users_job_test.rb +26 -0
- metadata +27 -3
- data/test/dummy/config/database.gitlab-ci.yml +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf27c28d441f98e6eefdc7809f69daca79c75604
|
4
|
+
data.tar.gz: 9686a13bb881a26dd0185eb5f7673c211c8eebb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4a2afea46cb6cbbf4e33baf5cbeb2b533ba6f9b9032b40f6a76d984a18977179fedf1fc656c1f750fc383e54906cb824f633447b7481f42267950c7ad921d16
|
7
|
+
data.tar.gz: 8f90a604e1a61b4b9b5f617dbd7d77a0406ccca51d82d6ab46638b2ab1293b3b1483031a2e33051cf0770a46dbf8b44bc33ab7aaf79fd947bf9c034064ca474b
|
@@ -2,7 +2,7 @@ class ModelForm extends BaseForm {
|
|
2
2
|
|
3
3
|
render() {
|
4
4
|
const { modelTitle, modelName, modelUrl, modelsUrl, children, authenticityToken } = this.props;
|
5
|
-
const
|
5
|
+
const errorsElement = this.getErrorsElement();
|
6
6
|
|
7
7
|
return(
|
8
8
|
<form action={modelUrl ? modelUrl : modelsUrl} acceptCharset="UTF-8" method="POST" data-shopify-app-submit="ea.save">
|
@@ -10,12 +10,7 @@ class ModelForm extends BaseForm {
|
|
10
10
|
<input type="hidden" name="_method" value={modelUrl ? 'patch' : 'post'} />
|
11
11
|
<input type="hidden" name="authenticity_token" value={authenticityToken}/>
|
12
12
|
|
13
|
-
{
|
14
|
-
if (!errors) return false;
|
15
|
-
return (
|
16
|
-
{errors}
|
17
|
-
);
|
18
|
-
})()}
|
13
|
+
{errorsElement}
|
19
14
|
|
20
15
|
{children}
|
21
16
|
|
@@ -5,64 +5,36 @@
|
|
5
5
|
class BaseForm extends React.Component {
|
6
6
|
|
7
7
|
/**
|
8
|
-
*
|
9
|
-
*
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}
|
18
|
-
|
19
|
-
/**
|
20
|
-
* returns a list of fields that contain errors
|
21
|
-
**/
|
22
|
-
errorKeys() {
|
23
|
-
return this.props.errors.errors;
|
24
|
-
}
|
25
|
-
|
26
|
-
/**
|
27
|
-
* returns the type of resource associate with this error
|
28
|
-
**/
|
29
|
-
errorType() {
|
30
|
-
return this.props.errors.type;
|
31
|
-
}
|
32
|
-
|
33
|
-
/**
|
34
|
-
* returns the error messages
|
35
|
-
**/
|
36
|
-
errorMessages() {
|
37
|
-
return this.props.errors.messages;
|
38
|
-
}
|
39
|
-
|
40
|
-
/**
|
41
|
-
* renders basic form errors
|
42
|
-
**/
|
43
|
-
renderErrors() {
|
44
|
-
if (!this.hasErrors()) {
|
8
|
+
* Returns the JSX required to render a list of errors.
|
9
|
+
*
|
10
|
+
* @returns {*}
|
11
|
+
*/
|
12
|
+
getErrorsElement() {
|
13
|
+
const { errors } = this.props;
|
14
|
+
|
15
|
+
// Don't render anything if no errors present.
|
16
|
+
if(!errors || !errors.errors || (errors.errors.length == 0)) {
|
45
17
|
return null;
|
46
18
|
}
|
47
19
|
|
48
|
-
|
49
|
-
|
50
|
-
return <li>{message}</li>;
|
51
|
-
});
|
20
|
+
const errorCount = errors.errors.length;
|
21
|
+
const singleError = (errorCount == 1);
|
52
22
|
|
53
23
|
return (
|
54
24
|
<div className="ui-banner ui-banner--status-error ui-banner--default-vertical-spacing ui-banner--default-horizontal-spacing">
|
55
25
|
<div className="ui-banner__ribbon">
|
56
26
|
<svg className="next-icon next-icon--24" viewBox="0 0 24 24">
|
57
|
-
<
|
27
|
+
<use xmlns="http://www.w3.org/1999/xlink" xlinkHref="#next-error" />
|
58
28
|
</svg>
|
59
29
|
</div>
|
60
30
|
<div className="ui-banner__content">
|
61
31
|
<h2 className="ui-banner__title">
|
62
|
-
{
|
32
|
+
There {singleError ? 'is' : 'are'} {errorCount} error{singleError ? '' : 's'} for this {errors.type}:
|
63
33
|
</h2>
|
64
34
|
<ul>
|
65
|
-
{
|
35
|
+
{errors.messages.map((message, i) => {
|
36
|
+
return <li key={i}>{message}</li>;
|
37
|
+
})}
|
66
38
|
</ul>
|
67
39
|
</div>
|
68
40
|
</div>
|
@@ -1,6 +1,6 @@
|
|
1
|
-
const InputCheckbox = ({ label, name,
|
1
|
+
const InputCheckbox = ({ label, name, checked, inline, isLast, onChange, disabled = false }) => {
|
2
2
|
|
3
|
-
const id =
|
3
|
+
const id = name;
|
4
4
|
|
5
5
|
const wrapperClassName = classNames({
|
6
6
|
'next-input-wrapper': true,
|
@@ -16,14 +16,19 @@ const InputCheckbox = ({ label, name, value, checked, inline, isLast, onChange,
|
|
16
16
|
});
|
17
17
|
|
18
18
|
const handleChange = (e) => {
|
19
|
-
onChange && onChange(e.target.
|
19
|
+
onChange && onChange(e.target.checked);
|
20
20
|
};
|
21
21
|
|
22
22
|
return(
|
23
23
|
<div className={wrapperClassName}>
|
24
24
|
<label htmlFor={id} className={labelClassName}>{label}</label>
|
25
|
-
<input
|
26
|
-
<
|
25
|
+
<input type="hidden" value="0" name={name} />
|
26
|
+
<input id={id} className="next-checkbox" type="checkbox" value="1" name={name} checked={checked} onChange={handleChange} disabled={disabled} />
|
27
|
+
<span className="next-checkbox--styled">
|
28
|
+
<svg className="next-icon next-icon--size-10 next-icon--blue checkmark">
|
29
|
+
<use xmlns="http://www.w3.org/1999/xlink" xlinkHref="#next-checkmark" />
|
30
|
+
</svg>
|
31
|
+
</span>
|
27
32
|
</div>
|
28
33
|
)
|
29
34
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module DiscoApp::Admin::Concerns::SourcesController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :find_source, only: [:edit, :update, :destroy]
|
6
|
+
end
|
7
|
+
|
8
|
+
def index
|
9
|
+
@sources = DiscoApp::Source.all
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
@source = DiscoApp::Source.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
@source = DiscoApp::Source.new(source_params)
|
18
|
+
if @source.save
|
19
|
+
redirect_to admin_sources_path
|
20
|
+
else
|
21
|
+
render 'new'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def edit
|
26
|
+
end
|
27
|
+
|
28
|
+
def update
|
29
|
+
if @source.update_attributes(source_params)
|
30
|
+
redirect_to edit_admin_plan_path(@source)
|
31
|
+
else
|
32
|
+
render 'edit'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
source.destroy
|
38
|
+
redirect_to admin_sources_path
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def find_source
|
44
|
+
@source = DiscoApp::Source.find(params[:id])
|
45
|
+
end
|
46
|
+
|
47
|
+
def source_params
|
48
|
+
params.require(:source).permit(:source, :name)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DiscoApp::Concerns::UserAuthenticatedController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include ShopifyApp::LoginProtection
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_action :shopify_user
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def shopify_user
|
12
|
+
@user = DiscoApp::User.find(session[:shopify_user])
|
13
|
+
rescue ActiveRecord::RecordNotFound
|
14
|
+
redirect_to disco_app.new_user_session_path
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class DiscoApp::UserSessionsController < ApplicationController
|
2
|
+
include DiscoApp::Concerns::AuthenticatedController
|
3
|
+
|
4
|
+
def new
|
5
|
+
authenticate if sanitized_shop_name.present?
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
authenticate
|
10
|
+
end
|
11
|
+
|
12
|
+
def callback
|
13
|
+
if auth_hash
|
14
|
+
login_user
|
15
|
+
redirect_to return_address
|
16
|
+
else
|
17
|
+
redirect_to root_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy
|
22
|
+
session[:shopify_user] = nil
|
23
|
+
redirect_to root_path
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def auth_hash
|
29
|
+
request.env['omniauth.auth']
|
30
|
+
end
|
31
|
+
|
32
|
+
def associated_user(auth_hash)
|
33
|
+
auth_hash['extra']['associated_user']
|
34
|
+
end
|
35
|
+
|
36
|
+
def authenticate
|
37
|
+
if sanitized_shop_name.present?
|
38
|
+
fullpage_redirect_to "#{main_app.root_path}auth/shopify_user?shop=#{sanitized_shop_name}"
|
39
|
+
else
|
40
|
+
redirect_to return_address
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def login_user
|
45
|
+
@user = DiscoApp::User.create_user(associated_user(auth_hash), @shop)
|
46
|
+
session[:shopify_user] = @user.id
|
47
|
+
end
|
48
|
+
|
49
|
+
def return_address
|
50
|
+
session.delete(:return_to) || main_app.root_url
|
51
|
+
end
|
52
|
+
|
53
|
+
def sanitized_shop_name
|
54
|
+
@shop.shopify_domain
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -14,7 +14,7 @@ module DiscoApp::Concerns::AppInstalledJob
|
|
14
14
|
# - Perform initial update of shop information.
|
15
15
|
# - Subscribe to default plan, if any exists.
|
16
16
|
#
|
17
|
-
def perform(
|
17
|
+
def perform(_shop, plan_code = nil, source = nil)
|
18
18
|
DiscoApp::SynchroniseWebhooksJob.perform_now(@shop)
|
19
19
|
DiscoApp::SynchroniseCarrierServiceJob.perform_now(@shop)
|
20
20
|
DiscoApp::ShopUpdateJob.perform_now(@shop)
|
@@ -12,7 +12,7 @@ module DiscoApp::Concerns::AppUninstalledJob
|
|
12
12
|
# - Mark any recurring application charges as cancelled.
|
13
13
|
# - Remove any stored sessions for the shop.
|
14
14
|
#
|
15
|
-
def perform(
|
15
|
+
def perform(_shop, shop_data)
|
16
16
|
DiscoApp::ChargesService.cancel_recurring_charges(@shop)
|
17
17
|
DiscoApp::SendSubscriptionJob.perform_later(@shop)
|
18
18
|
@shop.sessions.delete_all
|
@@ -2,7 +2,7 @@ module DiscoApp::Concerns::ShopUpdateJob
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
# Perform an update of the current shop's information.
|
5
|
-
def perform(
|
5
|
+
def perform(_shop, shop_data = nil)
|
6
6
|
# If we weren't provided with shop data (eg from a webhook), fetch it.
|
7
7
|
shop_data ||= ActiveSupport::JSON::decode(ShopifyAPI::Shop.current.to_json)
|
8
8
|
|
@@ -2,7 +2,7 @@ module DiscoApp::Concerns::SynchroniseCarrierServiceJob
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
# Ensure that any carrier service required by our app is registered.
|
5
|
-
def perform(
|
5
|
+
def perform(_shop)
|
6
6
|
# Don't proceed unless we have a name and callback url.
|
7
7
|
return unless carrier_service_name and callback_url
|
8
8
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DiscoApp::Concerns::SynchroniseResourcesJob
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
def perform(
|
4
|
+
def perform(_shop, class_name, params)
|
5
5
|
klass = class_name.constantize
|
6
6
|
|
7
7
|
klass::SHOPIFY_API_CLASS.find(:all, params: params).map do |shopify_resource|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module DiscoApp::Concerns::SynchroniseUsersJob
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def perform(_shop)
|
5
|
+
begin
|
6
|
+
users = @shop.with_api_context {
|
7
|
+
ShopifyAPI::User.all
|
8
|
+
}
|
9
|
+
rescue ActiveResource::UnauthorizedAccess => e
|
10
|
+
Rollbar.error(e) and return
|
11
|
+
end
|
12
|
+
users.each { |user| DiscoApp::User.create_user(user, @shop) }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -3,7 +3,7 @@ module DiscoApp::Concerns::SynchroniseWebhooksJob
|
|
3
3
|
|
4
4
|
# Ensure the webhooks registered with our shop are the same as those listed
|
5
5
|
# in our application configuration.
|
6
|
-
def perform(
|
6
|
+
def perform(_shop)
|
7
7
|
# Get the full list of expected webhook topics.
|
8
8
|
expected_topics = [:'app/uninstalled', :'shop/update'] + (DiscoApp.configuration.webhook_topics || [])
|
9
9
|
|
@@ -2,6 +2,9 @@
|
|
2
2
|
# particular Shop's API session. The first argument to any job inheriting from
|
3
3
|
# this class must be the domain of the relevant store, so that the appropriate
|
4
4
|
# Shop model can be fetched and the temporary API session created.
|
5
|
+
|
6
|
+
require 'rollbar'
|
7
|
+
|
5
8
|
class DiscoApp::ShopJob < ActiveJob::Base
|
6
9
|
|
7
10
|
queue_as :default
|
@@ -19,9 +22,13 @@ class DiscoApp::ShopJob < ActiveJob::Base
|
|
19
22
|
end
|
20
23
|
|
21
24
|
def shop_context(job, block)
|
22
|
-
|
23
|
-
block.call(job.arguments)
|
24
|
-
|
25
|
+
Rollbar.scoped(rollbar_scope) do
|
26
|
+
@shop.with_api_context { block.call(job.arguments) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def rollbar_scope
|
31
|
+
{ person: { id: @shop.id, username: @shop.shopify_domain } }
|
25
32
|
end
|
26
33
|
|
27
34
|
end
|
@@ -9,6 +9,9 @@ module DiscoApp::Concerns::Shop
|
|
9
9
|
has_many :subscriptions
|
10
10
|
has_many :plans, through: :subscriptions
|
11
11
|
|
12
|
+
# Define relationship to users.
|
13
|
+
has_many :users
|
14
|
+
|
12
15
|
# Define relationship to sessions.
|
13
16
|
has_many :sessions, class_name: 'DiscoApp::Session', dependent: :destroy
|
14
17
|
|
@@ -19,6 +22,7 @@ module DiscoApp::Concerns::Shop
|
|
19
22
|
scope :status, -> (status) { where status: status }
|
20
23
|
scope :installed, -> { where status: statuses[:installed] }
|
21
24
|
scope :has_active_shopify_plan, -> { where.not(plan_name: [:cancelled, :frozen, :fraudulent]) }
|
25
|
+
scope :shopify_plus, -> { where(plan_name: :shopify_plus) }
|
22
26
|
|
23
27
|
# Alias 'with_shopify_session' as 'with_api_context' for better readability, but also as 'temp' for
|
24
28
|
# backward compatibility.
|
@@ -27,7 +31,7 @@ module DiscoApp::Concerns::Shop
|
|
27
31
|
|
28
32
|
# Return true if the shop is considered as in development mode.
|
29
33
|
def development?
|
30
|
-
['staff', '
|
34
|
+
['staff', 'affiliate'].include?(plan_name)
|
31
35
|
end
|
32
36
|
|
33
37
|
# Convenience method to check if this shop has a current subscription.
|
@@ -63,7 +67,7 @@ module DiscoApp::Concerns::Shop
|
|
63
67
|
|
64
68
|
# Convenience method to get the email of the shop's admin, to display in Rollbar.
|
65
69
|
def email
|
66
|
-
|
70
|
+
data[:email]
|
67
71
|
end
|
68
72
|
|
69
73
|
def installed_duration
|
@@ -74,7 +78,7 @@ module DiscoApp::Concerns::Shop
|
|
74
78
|
# shop's "data" hash, return the default Rails zone (which should be UTC).
|
75
79
|
def time_zone
|
76
80
|
@time_zone ||= begin
|
77
|
-
Time.find_zone!(data[
|
81
|
+
Time.find_zone!(data[:timezone].to_s.gsub(/^\(.+\)\s/, ''))
|
78
82
|
rescue ArgumentError
|
79
83
|
Time.zone
|
80
84
|
end
|
@@ -83,7 +87,7 @@ module DiscoApp::Concerns::Shop
|
|
83
87
|
# Return the shop's configured locale as a symbol. If none exists for some
|
84
88
|
# reason, 'en' is returned.
|
85
89
|
def locale
|
86
|
-
(data[
|
90
|
+
(data[:primary_locale] || 'en').to_sym
|
87
91
|
end
|
88
92
|
|
89
93
|
# Return an instance of the Disco API client.
|
@@ -91,6 +95,11 @@ module DiscoApp::Concerns::Shop
|
|
91
95
|
@api_client ||= DiscoApp::ApiClient.new(self, ENV['DISCO_API_URL'])
|
92
96
|
end
|
93
97
|
|
98
|
+
# Override the "read" data attribute to allow indifferent access.
|
99
|
+
def data
|
100
|
+
read_attribute(:data).with_indifferent_access
|
101
|
+
end
|
102
|
+
|
94
103
|
end
|
95
104
|
|
96
105
|
end
|
@@ -6,7 +6,7 @@ module DiscoApp::Concerns::Subscription
|
|
6
6
|
belongs_to :shop
|
7
7
|
belongs_to :plan
|
8
8
|
belongs_to :plan_code
|
9
|
-
|
9
|
+
belongs_to :source
|
10
10
|
has_many :one_time_charges, class_name: 'DiscoApp::ApplicationCharge', dependent: :destroy
|
11
11
|
has_many :recurring_charges, class_name: 'DiscoApp::RecurringApplicationCharge', dependent: :destroy
|
12
12
|
|
@@ -2,15 +2,19 @@ module DiscoApp::Concerns::Taggable
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
def tags
|
5
|
-
data[
|
5
|
+
data[:tags].split(',').map(&:strip)
|
6
6
|
end
|
7
7
|
|
8
8
|
def add_tag(tag)
|
9
|
-
data[
|
9
|
+
data[:tags] = (tags + [tag]).uniq.join(',')
|
10
10
|
end
|
11
11
|
|
12
12
|
def remove_tag(tag)
|
13
|
-
data[
|
13
|
+
data[:tags] = (tags - [tag]).uniq.join(',')
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_tag?(tag_to_check)
|
17
|
+
tags.any? { |tag| tag.casecmp(tag_to_check) }
|
14
18
|
end
|
15
19
|
|
16
20
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DiscoApp::Concerns::User
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
belongs_to :shop
|
6
|
+
|
7
|
+
def self.create_user(shopify_user, shop)
|
8
|
+
user = self.find_or_create_by!(id: shopify_user.id, shop: shop)
|
9
|
+
user.update(
|
10
|
+
first_name: shopify_user.first_name || '',
|
11
|
+
last_name: shopify_user.last_name || '',
|
12
|
+
email: shopify_user.email
|
13
|
+
)
|
14
|
+
user
|
15
|
+
rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation
|
16
|
+
retry
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -46,19 +46,19 @@ module DiscoApp::Admin::Resources::Concerns::ShopResource
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def email
|
49
|
-
@model.data[
|
49
|
+
@model.data[:email]
|
50
50
|
end
|
51
51
|
|
52
52
|
def country_name
|
53
|
-
@model.data[
|
53
|
+
@model.data[:country_name]
|
54
54
|
end
|
55
55
|
|
56
56
|
def currency
|
57
|
-
@model.data[
|
57
|
+
@model.data[:currency]
|
58
58
|
end
|
59
59
|
|
60
60
|
def plan_display_name
|
61
|
-
@model.data[
|
61
|
+
@model.data[:plan_display_name]
|
62
62
|
end
|
63
63
|
|
64
64
|
def current_subscription_id
|
@@ -2,7 +2,7 @@ class DiscoApp::SubscriptionService
|
|
2
2
|
|
3
3
|
# Subscribe the given shop to the given plan, optionally using the given plan
|
4
4
|
# code and optionally tracking the subscription source.
|
5
|
-
def self.subscribe(shop, plan, plan_code = nil,
|
5
|
+
def self.subscribe(shop, plan, plan_code = nil, source_name = nil)
|
6
6
|
|
7
7
|
# If a plan code was provided, fetch it for the given plan.
|
8
8
|
plan_code_instance = nil
|
@@ -10,6 +10,12 @@ class DiscoApp::SubscriptionService
|
|
10
10
|
plan_code_instance = DiscoApp::PlanCode.available.find_by(plan: plan, code: plan_code)
|
11
11
|
end
|
12
12
|
|
13
|
+
# If a source name has been provided, fetch or create it
|
14
|
+
source_instance = nil
|
15
|
+
if source_name.present?
|
16
|
+
source_instance = DiscoApp::Source.find_or_create_by(source: source_name)
|
17
|
+
end
|
18
|
+
|
13
19
|
# Cancel any existing current subscriptions.
|
14
20
|
shop.subscriptions.current.update_all(
|
15
21
|
status: DiscoApp::Subscription.statuses[:cancelled],
|
@@ -33,7 +39,7 @@ class DiscoApp::SubscriptionService
|
|
33
39
|
trial_period_days: plan.has_trial? ? subscription_trial_period_days : nil,
|
34
40
|
trial_start_at: plan.has_trial? ? Time.now : nil,
|
35
41
|
trial_end_at: plan.has_trial? ? subscription_trial_period_days.days.from_now : nil,
|
36
|
-
source:
|
42
|
+
source: source_instance
|
37
43
|
)
|
38
44
|
|
39
45
|
# Enqueue the subscription changed background job.
|