code-complete minus a few features
This commit is contained in:
parent
64fdf3028a
commit
cf00671123
|
|
@ -78,7 +78,7 @@ gem 'slim'
|
|||
#gem 'influxdb', '0.1.8'
|
||||
#gem 'influxdb-rails', '0.1.10'
|
||||
gem 'influxdb-rails'
|
||||
gem 'recurly'
|
||||
gem 'recurly', '~> 2'
|
||||
gem 'sendgrid_toolkit', '>= 1.1.1'
|
||||
gem 'stripe'
|
||||
gem 'zip-codes'
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ GEM
|
|||
rb-fsevent (0.10.2)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
recurly (2.12.1)
|
||||
recurly (2.18.18)
|
||||
redis (4.0.1)
|
||||
redis-namespace (1.6.0)
|
||||
redis (>= 3.0.4)
|
||||
|
|
@ -665,7 +665,7 @@ DEPENDENCIES
|
|||
rails (> 4.2)
|
||||
rails-jquery-autocomplete
|
||||
rails-observers
|
||||
recurly
|
||||
recurly (~> 2)
|
||||
resque
|
||||
resque-failed-job-mailer
|
||||
resque-lonely_job (~> 1.0.0)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,41 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
|||
end
|
||||
|
||||
|
||||
#Notification.send_reload(connection.client_id)
|
||||
|
||||
member_action :give_free_plan, :method => :get do
|
||||
@client = RecurlyClient.new
|
||||
plan_code = params[:plan_code]
|
||||
if params[:plan_code] == ''
|
||||
plan_code = nil
|
||||
end
|
||||
|
||||
resource.update_admin_override_plan_code(plan_code)
|
||||
redirect_to :back, {notice: "User got a free plan via adminstrative override to #{params[:plan_code]}"}
|
||||
end
|
||||
|
||||
member_action :revoke_free_plan, :method => :get do
|
||||
resource.update_admin_override_plan_code(nil)
|
||||
redirect_to :back, {notice: "User has administrative free plan removed"}
|
||||
end
|
||||
|
||||
|
||||
member_action :sync_subscription, :method => :get do
|
||||
@client = RecurlyClient.new
|
||||
@client.sync_subscription(resource)
|
||||
redirect_to :back, {notice: "Check the Subscription Plan Code, Subscription Sync Code, Subscription Sync Msg"}
|
||||
end
|
||||
|
||||
member_action :change_to_plan, :method => :get do
|
||||
@client = RecurlyClient.new
|
||||
plan_code = params[:plan_code]
|
||||
if params[:plan_code] == ''
|
||||
plan_code = nil
|
||||
end
|
||||
|
||||
result, subscription, account = @client.update_desired_subscription(resource, plan_code)
|
||||
redirect_to :back, {notice: "Set user's desired plan. "}
|
||||
end
|
||||
member_action :quick_reset, :method => :get do
|
||||
resetting_to = 'jellyandjam123'
|
||||
resource.change_password(resetting_to, resetting_to)
|
||||
|
|
@ -35,6 +70,18 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
|||
redirect_to :back, {notice: "Reset password url created: #{reset_url}"}
|
||||
end
|
||||
|
||||
member_action :end_trial, :method => :get do
|
||||
if DateTime.now - 2.days < resource.subscription_trial_ends_at
|
||||
resource.subscription_trial_ends_at = 3.days.ago
|
||||
resource.save!
|
||||
redirect_to :back, {notice: "User's trial ended"}
|
||||
else
|
||||
redirect_to :back, {notice: "Users trial already ended > 2 days ago. Left alone user account"}
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
show do |user|
|
||||
|
|
@ -89,7 +136,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
|||
|
||||
row "Quick Password Reset" do |user|
|
||||
span do
|
||||
link_to("Reset Password to jellyandjam123", quick_reset_admin_user_path(user.id), :data => {:confirm => 'Reset password to jellyandjam123 ?'})
|
||||
link_to("reset password to jellyandjam123", quick_reset_admin_user_path(user.id), :data => {:confirm => 'Reset password to jellyandjam123 ?'})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,7 +148,106 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
|||
|
||||
row "Password Reset URL" do |user|
|
||||
span do
|
||||
link_to("Create Reset URL", create_reset_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'})
|
||||
link_to("create reset URL", create_reset_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
row "Subscription" do |user|
|
||||
div do
|
||||
attributes_table do
|
||||
row :desired_plan_code, 'hi'
|
||||
row :subscription_plan_code
|
||||
row :admin_override_plan_code
|
||||
row :recurly_subscription_state
|
||||
row :recurly_subscription_id
|
||||
row :desired_plan_code_set_at
|
||||
row :subscription_plan_code_set_at
|
||||
row :subscription_last_checked_at
|
||||
row :subscription_trial_ends_at
|
||||
row :subscription_sync_code
|
||||
row :subscription_sync_msg
|
||||
row :is_past_due
|
||||
row :stored_credit_card
|
||||
end
|
||||
end
|
||||
div do
|
||||
'DESIRED PLAN CODE = What plan the user has selected in the UI'
|
||||
end
|
||||
div do
|
||||
'SUBSCIRPTION PLAN CODE = What plan the user actually has'
|
||||
end
|
||||
div do
|
||||
div do
|
||||
user.recurly_code ? link_to('Recurly Account', user.recurly_link_to_account, target: "_blank", ) : span do 'No Recurly Account' end
|
||||
end
|
||||
div do
|
||||
user.recurly_subscription_id ? link_to('Recurly Subscription', user.recurly_link_to_subscription, target: "_blank", ) : span do 'No Recurly Subscription' end
|
||||
end
|
||||
end
|
||||
div do
|
||||
h3 do
|
||||
'Give Free Plan Actions'
|
||||
end
|
||||
h4 do
|
||||
'sets secret override to give user a free plan'
|
||||
end
|
||||
div do
|
||||
link_to("give free silver plan", give_free_plan_admin_user_path(user.id, plan_code: 'jamsubsilver'), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
div do
|
||||
link_to("give free gold plan", give_free_plan_admin_user_path(user.id, plan_code: 'jamsubgold'), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
div do
|
||||
link_to("give free platinum plan", give_free_plan_admin_user_path(user.id, plan_code: 'jamsubplatinum'), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
div do
|
||||
link_to("revoke free plan", revoke_free_plan_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
end
|
||||
div do
|
||||
h3 do
|
||||
'Change Plan Actions'
|
||||
end
|
||||
h4 do
|
||||
'exactly as if the user did it in the UI'
|
||||
end
|
||||
div do
|
||||
link_to("change plan to silver", change_to_plan_admin_user_path(user.id, 'jamsubsilver'), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
div do
|
||||
link_to("change plan to gold", change_to_plan_admin_user_path(user.id, plan_code: 'jamsubgold'), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
div do
|
||||
link_to("change plan to platinum", change_to_plan_admin_user_path(user.id, plan_code: 'jamsubplatinum'), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
div do
|
||||
link_to("change plan to free", change_to_plan_admin_user_path(user.id, plan_code: ''), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
|
||||
end
|
||||
div do
|
||||
h3 do
|
||||
'Force Sync'
|
||||
end
|
||||
h4 do
|
||||
'exactly same as background job that checks accounts'
|
||||
end
|
||||
div do
|
||||
link_to("force sync", sync_subscription_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
end
|
||||
|
||||
div do
|
||||
h3 do
|
||||
'End Trial'
|
||||
end
|
||||
h4 do
|
||||
'ends the trial as of 3 days ago'
|
||||
end
|
||||
div do
|
||||
link_to("end trial", end_trial_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,11 @@ module JamAdmin
|
|||
config.external_protocol = ENV['EXTERNAL_PROTOCOL'] || 'http://'
|
||||
config.external_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.external_port == 80 || config.external_port == 443) ? '' : ':' + config.external_port.to_s}"
|
||||
config.recurly_root_url = 'https://jamkazam-development.recurly.com'
|
||||
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
||||
config.recurly_private_api_key = '55f2fdfa4d014e64a94eaba1e93f39bb'
|
||||
# Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more.
|
||||
config.recurly_public_api_key = 'ewr1-HciusxMNfSSjz5WlupGk0C'
|
||||
|
||||
|
||||
# where is rabbitmq?
|
||||
config.rabbitmq_host = "127.0.0.1"
|
||||
|
|
@ -144,9 +149,9 @@ module JamAdmin
|
|||
config.max_track_part_upload_failures = 3
|
||||
|
||||
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
||||
config.recurly_private_api_key = '7d623daabfc2434fa2a893bb008eb3e6'
|
||||
config.recurly_private_api_key = '55f2fdfa4d014e64a94eaba1e93f39bb'
|
||||
# Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more.
|
||||
config.recurly_public_api_key = 'sc-SZlO11shkeA1WMGuISLGg5'
|
||||
config.recurly_public_api_key = 'ewr1-HciusxMNfSSjz5WlupGk0C'
|
||||
|
||||
# these values work out of the box with default settings of an influx install (you do have to add a development database by hand though)
|
||||
config.influxdb_database = "development"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ JamAdmin::Application.configure do
|
|||
# Show the logging configuration on STDOUT
|
||||
config.show_log_configuration = true
|
||||
|
||||
config.recurly_subdomain = 'jamkazam-development'
|
||||
config.email_support_alias = 'support-dev@jamkazam.com'
|
||||
config.email_generic_from = 'nobody-dev@jamkazam.com'
|
||||
config.email_alerts_alias = 'alerts-dev@jamkazam.com'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
Recurly.api_key = Rails.configuration.recurly_private_api_key
|
||||
Recurly.subdomain = Rails.configuration.recurly_subdomain
|
||||
Recurly.default_currency = 'USD'
|
||||
Recurly.logger = Rails.logger
|
||||
|
||||
Recurly::API.net_http = {
|
||||
ssl_version: :TLSv1_2,
|
||||
#...
|
||||
}
|
||||
|
|
@ -97,28 +97,41 @@ ALTER TABLE users ADD COLUMN recurly_subscription_id VARCHAR(100) DEFAULT NULL;
|
|||
ALTER TABLE users ADD COLUMN recurly_token VARCHAR(200) DEFAULT NULL;
|
||||
ALTER TABLE users ADD COLUMN recurly_subscription_state VARCHAR(20) DEFAULT NULL;
|
||||
ALTER TABLE users ADD COLUMN subscription_plan_code VARCHAR(100) DEFAULT NULL;
|
||||
CREATE TABLE subscriptions (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(200) UNIQUE NOT NULL UNIQUE NOT NULL,
|
||||
play_time_per_session_mins INT DEFAULT NULL,
|
||||
play_time_per_month_mins INT DEFAULT NULL,
|
||||
can_record BOOLEAN DEFAULT TRUE,
|
||||
audio_max_bitrate INT DEFAULT NULL,
|
||||
save_as_wave BOOLEAN DEFAULT FALSE,
|
||||
pro_audio BOOLEAN DEFAULT FALSE,
|
||||
video_resolution VARCHAR(50) DEFAULT NULL,
|
||||
broadcasting_type VARCHAR(50) DEFAULT NULL,
|
||||
music_lessons VARCHAR(50) DEFAULT NULL,
|
||||
support VARCHAR(50) DEFAULT NULL,
|
||||
max_players_per_session INT DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE users ADD COLUMN desired_plan_code VARCHAR(100) DEFAULT NULL;
|
||||
ALTER TABLE users ADD COLUMN admin_override_plan_code VARCHAR(100) DEFAULT NULL;
|
||||
ALTER TABLE users ADD COLUMN desired_plan_code_set_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN subscription_plan_code_set_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN subscription_last_checked_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN subscription_sync_code VARCHAR;
|
||||
ALTER TABLE users ADD COLUMN subscription_sync_msg VARCHAR;
|
||||
ALTER TABLE users ADD COLUMN client_fingerprint VARCHAR(255);
|
||||
ALTER TABLE users ADD COLUMN is_past_due BOOLEAN DEFAULT FALSE;
|
||||
CREATE INDEX subscription_sync_code_user_index ON users USING btree(subscription_sync_code);
|
||||
ALTER TABLE users ADD COLUMN subscription_trial_ends_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN subscription_plan_reason varchar(20);
|
||||
UPDATE users set subscription_trial_ends_at = (CURRENT_TIMESTAMP + '30 days'::interval), subscription_plan_code = 'jamsubgold';
|
||||
|
||||
CREATE INDEX msuh_user_id ON music_sessions_user_history((1)) WHERE is_a_student;
|
||||
|
||||
-- CREATE TABLE subscriptions (
|
||||
-- id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
-- name VARCHAR(200) UNIQUE NOT NULL UNIQUE NOT NULL,
|
||||
-- play_time_per_session_mins INT DEFAULT NULL,
|
||||
-- play_time_per_month_mins INT DEFAULT NULL,
|
||||
-- can_record BOOLEAN DEFAULT TRUE,
|
||||
-- audio_max_bitrate INT DEFAULT NULL,
|
||||
-- save_as_wave BOOLEAN DEFAULT FALSE,
|
||||
-- pro_audio BOOLEAN DEFAULT FALSE,
|
||||
-- video_resolution VARCHAR(50) DEFAULT NULL,
|
||||
-- broadcasting_type VARCHAR(50) DEFAULT NULL,
|
||||
-- music_lessons VARCHAR(50) DEFAULT NULL,
|
||||
-- support VARCHAR(50) DEFAULT NULL,
|
||||
-- max_players_per_session INT DEFAULT NULL,
|
||||
-- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
-- updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
-- );
|
||||
|
||||
|
||||
--CREATE INDEX msuh_user_id ON music_sessions_user_history((1)) WHERE is_a_student;
|
||||
|
||||
-- alreday on WWW
|
||||
CREATE INDEX msuh_user_id ON music_sessions_user_history USING btree (user_id);
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ CREATE OR REPLACE FUNCTION generate_scores_dataset () RETURNS VOID STRICT VOLATI
|
|||
DELETE FROM jamisp;
|
||||
INSERT INTO jamisp (beginip, endip, coid) SELECT x.beginip, x.endip, y.coid FROM geoipisp x, jamcompany y WHERE x.company = y.company;
|
||||
|
||||
UPDATE geoiplocations SET geog = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography;
|
||||
UPDATE geoipblocks SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
UPDATE jamisp SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
--UPDATE geoiplocations SET geog = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography;
|
||||
--UPDATE geoipblocks SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
--UPDATE jamisp SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
|
||||
|
||||
IF EXISTS(
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ CREATE SCHEMA tiger;
|
|||
CREATE SCHEMA topology;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public;
|
||||
CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder WITH SCHEMA tiger;
|
||||
--CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public; -- don't use this anymore
|
||||
--CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder WITH SCHEMA tiger; -- don't use this anymore
|
||||
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
|
||||
--CREATE EXTENSION IF NOT EXISTS postgis_topology WITH SCHEMA topology;
|
||||
|
||||
|
|
|
|||
|
|
@ -106,19 +106,19 @@ DELETE FROM jamisp;
|
|||
INSERT INTO jamisp (beginip, endip, coid) SELECT x.beginip, x.endip, y.coid FROM geoipisp x, jamcompany y WHERE x.company = y.company;
|
||||
|
||||
--ALTER TABLE geoiplocations DROP COLUMN geog;
|
||||
ALTER TABLE geoiplocations ADD COLUMN geog geography(point, 4326);
|
||||
UPDATE geoiplocations SET geog = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography;
|
||||
CREATE INDEX geoiplocations_geog_gix ON geoiplocations USING GIST (geog);
|
||||
--ALTER TABLE geoiplocations ADD COLUMN geog geography(point, 4326);
|
||||
--UPDATE geoiplocations SET geog = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography;
|
||||
--CREATE INDEX geoiplocations_geog_gix ON geoiplocations USING GIST (geog);
|
||||
|
||||
--ALTER TABLE geoipblocks DROP COLUMN geom;
|
||||
ALTER TABLE geoipblocks ADD COLUMN geom geometry(polygon);
|
||||
UPDATE geoipblocks SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
CREATE INDEX geoipblocks_geom_gix ON geoipblocks USING GIST (geom);
|
||||
--ALTER TABLE geoipblocks ADD COLUMN geom geometry(polygon);
|
||||
--UPDATE geoipblocks SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
--CREATE INDEX geoipblocks_geom_gix ON geoipblocks USING GIST (geom);
|
||||
|
||||
--ALTER TABLE jamisp DROP COLUMN geom;
|
||||
ALTER TABLE jamisp ADD COLUMN geom geometry(polygon);
|
||||
UPDATE jamisp SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
CREATE INDEX jamisp_geom_gix ON jamisp USING GIST (geom);
|
||||
--ALTER TABLE jamisp ADD COLUMN geom geometry(polygon);
|
||||
--UPDATE jamisp SET geom = ST_MakeEnvelope(beginip, -1, endip, 1);
|
||||
--CREATE INDEX jamisp_geom_gix ON jamisp USING GIST (geom);
|
||||
|
||||
--DROP VIEW current_scores;
|
||||
CREATE VIEW current_scores AS SELECT * FROM scores s WHERE score_dt = (SELECT max(score_dt) FROM scores s0 WHERE s0.alocidispid = s.alocidispid AND s0.blocidispid = s.blocidispid);
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ message LoginAck {
|
|||
optional string username = 10;
|
||||
optional int32 client_id_int = 11;
|
||||
repeated Ars arses = 12;
|
||||
optional SiteSubscription subscription = 13;
|
||||
}
|
||||
|
||||
message ConnectAck {
|
||||
|
|
@ -826,6 +827,20 @@ message ClientUpdate {
|
|||
optional int32 size = 4;
|
||||
}
|
||||
|
||||
message SiteSubscription {
|
||||
optional int32 play_time_per_month = 1;
|
||||
optional int32 play_time_per_session = 2;
|
||||
optional bool can_record_audio = 3;
|
||||
optional bool can_use_video = 4;
|
||||
optional bool can_record_video = 5;
|
||||
optional bool can_record_wave = 6;
|
||||
optional int32 audio_max_bitrate = 7;
|
||||
optional int32 video_resolution = 8;
|
||||
optional bool can_broadcast = 9;
|
||||
optional int32 broadcasting_type = 10;
|
||||
optional int32 max_players = 11;
|
||||
}
|
||||
|
||||
message Ars {
|
||||
optional int32 id = 1;
|
||||
optional string ip = 2;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ module JamRuby
|
|||
)
|
||||
end
|
||||
# create a login ack (login was successful)
|
||||
def login_ack(public_ip, client_id, token, heartbeat_interval, music_session_id, reconnected, user_id, connection_expire_time, username, client_id_int, client_update_data = nil, arses = [])
|
||||
def login_ack(public_ip, client_id, token, heartbeat_interval, music_session_id, reconnected, user_id, connection_expire_time, username, client_id_int, client_update_data = nil, arses = [], subscription=nil)
|
||||
client_update = Jampb::ClientUpdate.new(
|
||||
product: client_update_data[:product],
|
||||
version: client_update_data[:version],
|
||||
|
|
@ -105,7 +105,20 @@ module JamRuby
|
|||
:client_update => client_update,
|
||||
:username => username,
|
||||
:client_id_int => client_id_int,
|
||||
:arses => arses
|
||||
:arses => arses,
|
||||
:subscription => Jampb::SiteSubscription.new(
|
||||
play_time_per_month: subscription[:play_time_per_month],
|
||||
play_time_per_session: subscription[:play_time_per_session],
|
||||
can_record_audio: subscription[:can_record_audio],
|
||||
can_use_video: subscription[:can_use_video],
|
||||
can_record_video: subscription[:can_record_video],
|
||||
can_record_wave: subscription[:can_record_wave],
|
||||
audio_max_bitrate: subscription[:audio_max_bitrate],
|
||||
video_resolution: subscription[:video_resolution],
|
||||
can_broadcast: subscription[:can_broadcast],
|
||||
broadcasting_type: subscription[:broadcasting_type],
|
||||
max_players: subscription[:max_players]
|
||||
)
|
||||
)
|
||||
|
||||
Jampb::ClientMessage.new(
|
||||
|
|
|
|||
|
|
@ -274,11 +274,11 @@ module JamRuby
|
|||
if sale.valid?
|
||||
|
||||
client = RecurlyClient.new
|
||||
# this is handled in update_payment now
|
||||
#account = client.find_or_create_account(current_user, nil, recurly_token)
|
||||
#client.update_billing_info_from_token(current_user, account, recurly_token)
|
||||
|
||||
account = client.find_or_create_account(current_user, nil, recurly_token)
|
||||
|
||||
client.update_billing_info_from_token(current_user, account, recurly_token)
|
||||
|
||||
account = client.get_account(current_user)
|
||||
if account.present?
|
||||
recurly_response = client.create_subscription(current_user, plan_code, account)
|
||||
current_user.recurly_subscription_id = recurly_response.uuid
|
||||
|
|
|
|||
|
|
@ -381,9 +381,21 @@ module JamRuby
|
|||
end
|
||||
|
||||
def self.hourly_check
|
||||
send_onboarding_surveys
|
||||
send_take_lesson_poke
|
||||
first_lesson_instructions
|
||||
#send_onboarding_surveys
|
||||
#send_take_lesson_poke
|
||||
#first_lesson_instructions
|
||||
subscription_sync
|
||||
end
|
||||
|
||||
def self.subscription_sync
|
||||
recurly_client = RecurlyClient.new
|
||||
User
|
||||
.where('subscription_last_checked_at is NULL OR users.subs < ?', 1.days.ago)
|
||||
.where("recurly_subscription_id IS NOT NULL OR (subscription_sync_code not in ('trial_ended', 'no_recurly_account', 'admin_control', 'school_license', 'no_subscription_or_expired'))")
|
||||
.order('subscription_last_checked_at ASC NULLS FIRST')
|
||||
.each do |user|
|
||||
recurly_client.sync_subscription(user)
|
||||
end
|
||||
end
|
||||
|
||||
def self.first_lesson_instructions
|
||||
|
|
@ -398,7 +410,6 @@ module JamRuby
|
|||
User.where(id: user.id).update_all(sent_first_lesson_instr_email_at: Time.now)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def self.send_onboarding_surveys
|
||||
|
|
@ -728,6 +739,10 @@ module JamRuby
|
|||
license_end && Time.now > license_end
|
||||
end
|
||||
|
||||
def has_active_license?
|
||||
license_end && !license_expired?
|
||||
end
|
||||
|
||||
def session_count
|
||||
0
|
||||
#MusicSession.where("user_id = ? AND started_at IS NOT NULL", self.id).size
|
||||
|
|
@ -1449,6 +1464,11 @@ module JamRuby
|
|||
license_start = options[:license_start]
|
||||
license_end = options[:license_end]
|
||||
import_source = options[:import_source]
|
||||
desired_plan_code = options[:desired_plan_code]
|
||||
|
||||
if desired_plan_code == ''
|
||||
desired_plan_code = nil
|
||||
end
|
||||
|
||||
test_drive_package = TestDrivePackage.find_by_name(test_drive_package_details[:name]) if test_drive_package_details
|
||||
|
||||
|
|
@ -1498,6 +1518,10 @@ module JamRuby
|
|||
user.retailer_interest = !!retailer_interest
|
||||
user.school_interest = !!school_interest
|
||||
user.education_interest = !!education_interest
|
||||
user.desired_plan_code = desired_plan_code
|
||||
user.subscription_plan_code = SubscriptionDefinitions::JAM_GOLD
|
||||
user.desired_plan_code_set_at = DateTime.now
|
||||
user.subscription_trial_ends_at = DateTime.now + 30.days
|
||||
if user.is_a_student || user.is_a_teacher
|
||||
musician = true
|
||||
end
|
||||
|
|
@ -2829,6 +2853,25 @@ module JamRuby
|
|||
rules
|
||||
end
|
||||
|
||||
def update_admin_override_plan_code(plan_code)
|
||||
self.admin_override_plan_code = plan_code
|
||||
self.subscription_plan_code = plan_code
|
||||
self.subscription_plan_code_set_at = DateTime.now
|
||||
|
||||
self.save(validate: false)
|
||||
end
|
||||
|
||||
def subscription_trial_ended?
|
||||
subscription_trial_ends_at.nil? || DateTime.now > subscription_trial_ends_at
|
||||
end
|
||||
|
||||
def recurly_link_to_account
|
||||
"https://#{APP_CONFIG.recurly_subdomain}.recurly.com/accounts/#{id}"
|
||||
end
|
||||
def recurly_link_to_subscription
|
||||
"https://#{APP_CONFIG.recurly_subdomain}.recurly.com/subscriptions/#{recurly_subscription_id}"
|
||||
end
|
||||
|
||||
private
|
||||
def create_remember_token
|
||||
self.remember_token = SecureRandom.urlsafe_base64
|
||||
|
|
|
|||
|
|
@ -44,14 +44,68 @@ module JamRuby
|
|||
account
|
||||
end
|
||||
|
||||
|
||||
def update_desired_subscription(current_user, plan_code)
|
||||
subscription = nil
|
||||
account = nil
|
||||
current_user.desired_plan_code = plan_code
|
||||
current_user.desired_plan_code_set_at = DateTime.now
|
||||
current_user.save(validate: false)
|
||||
|
||||
puts "updating desired subscription for #{current_user.email} to #{plan_code}"
|
||||
|
||||
account = get_account(current_user)
|
||||
|
||||
if account
|
||||
if plan_code.nil? || plan_code == ''
|
||||
begin
|
||||
# user wants a free subscription. If they have a subscription, let's cancel it.
|
||||
subscription, account = find_subscription(current_user, account)
|
||||
if subscription
|
||||
puts "Canceling user's #{current_user.email} subscription"
|
||||
subscription.cancel
|
||||
# do not delete the recurly_subscription_id ; we'll use that to try and reactivate later if they user re-activates their account
|
||||
else
|
||||
# if no subscription and past trial, you goin down -- because there must have never been payment??
|
||||
if current_user.subscription_trial_ended?
|
||||
current_user.subscription_plan_code = nil
|
||||
current_user.subscription_plan_code_set_at = DateTime.now
|
||||
current_user.save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
# do not set the subscription _plan_code either; because the user has paid through the month; they still
|
||||
# get their old plan
|
||||
#current_user.subscription_plan_code = nil
|
||||
#current_user.save(validate: false)
|
||||
rescue => e
|
||||
puts "Could not cancel subscription for user #{current_user.email}. #{e}"
|
||||
return false, subscription, account
|
||||
end
|
||||
else
|
||||
# user wants to pay. let's get it goin
|
||||
return handle_create_subscription(current_user, plan_code, account)
|
||||
end
|
||||
end
|
||||
|
||||
return true, subscription, account
|
||||
|
||||
end
|
||||
|
||||
|
||||
def get_account(current_user)
|
||||
account = current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
|
||||
|
||||
# check again, assuming account_code is the user ID (can happen in error scenarios where we create the account
|
||||
# on recurly, but couldn't save the account_code to the user.recurly_code field)
|
||||
|
||||
puts "get_account for #{current_user.email} found #{account}"
|
||||
if !account
|
||||
account = Recurly::Account.find(current_user.id)
|
||||
begin
|
||||
account = Recurly::Account.find(current_user.id)
|
||||
rescue Recurly::Error => x
|
||||
puts "Swallow find acct error #{x}"
|
||||
end
|
||||
|
||||
# repair user local account info
|
||||
if !account.nil?
|
||||
current_user.update_attribute(:recurly_code, account.account_code)
|
||||
|
|
@ -78,39 +132,92 @@ module JamRuby
|
|||
account
|
||||
end
|
||||
|
||||
def payment_history(current_user, options ={})
|
||||
def list_invoices(account)
|
||||
invoices = []
|
||||
count = 0
|
||||
account.invoices.find_each do |invoice|
|
||||
count = count + 1
|
||||
invoices << invoice
|
||||
if count == 50
|
||||
break
|
||||
end
|
||||
end
|
||||
invoices
|
||||
end
|
||||
|
||||
|
||||
def payment_history(current_user, params ={})
|
||||
|
||||
limit = params[:limit]
|
||||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
cursor = options[:cursor]
|
||||
cursor = params[:cursor]
|
||||
|
||||
payments = []
|
||||
account = get_account(current_user)
|
||||
if(account.present?)
|
||||
begin
|
||||
|
||||
account.transaction.paginate(per_page:limit, cursor:cursor).each do |transaction|
|
||||
account.transactions.paginate(per_page:limit, cursor:cursor).each do |transaction|
|
||||
# XXX this isn't correct because we create 0 dollar transactions too (for free stuff)
|
||||
#if transaction.amount_in_cents > 0 # Account creation adds a transaction record
|
||||
payments << {
|
||||
:created_at => transaction.created_at,
|
||||
:amount_in_cents => transaction.amount_in_cents,
|
||||
:tax_in_cents=> transaction.tax_in_cents,
|
||||
:status => transaction.status,
|
||||
:action => transaction.action,
|
||||
:payment_method => transaction.payment_method,
|
||||
:reference => transaction.reference,
|
||||
:plan_code => transaction.plan_code
|
||||
:currency => transaction.currency
|
||||
}
|
||||
#end
|
||||
end
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
puts "Recurly error #{current_user.email} #{x}"
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
end
|
||||
payments
|
||||
end
|
||||
|
||||
|
||||
def invoice_history(current_user, params ={})
|
||||
|
||||
limit = params[:limit]
|
||||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
cursor = params[:cursor]
|
||||
|
||||
payments = []
|
||||
account = get_account(current_user)
|
||||
if(account.present?)
|
||||
begin
|
||||
|
||||
account.invoices.paginate(per_page:limit, cursor:cursor).each do |invoice|
|
||||
# XXX this isn't correct because we create 0 dollar transactions too (for free stuff)
|
||||
#if transaction.amount_in_cents > 0 # Account creation adds a transaction record
|
||||
payments << {
|
||||
:created_at => invoice.created_at,
|
||||
:subtotal_in_cents => invoice.subtotal_in_cents,
|
||||
:tax_in_cents=> invoice.tax_in_cents,
|
||||
:total_in_cents => invoice.total_in_cents,
|
||||
:state => invoice.state,
|
||||
:description => invoice.line_items.map(&:description).join(", "),
|
||||
:currency => invoice.currency
|
||||
}
|
||||
#end
|
||||
end
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
puts "Recurly error #{current_user.email} #{x}"
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
end
|
||||
return payments, account
|
||||
end
|
||||
|
||||
def update_billing_info(current_user, billing_info=nil, account = nil)
|
||||
account = get_account(current_user) if account.nil?
|
||||
if account.present?
|
||||
|
|
@ -211,51 +318,124 @@ module JamRuby
|
|||
raise RecurlyClientError.new(plan.errors) if plan.errors.any?
|
||||
end
|
||||
|
||||
def handle_create_subscription(current_user, plan_code, account)
|
||||
begin
|
||||
subscription = create_subscription(current_user, plan_code, account, current_user.subscription_trial_ended? ? nil : current_user.subscription_trial_ends_at)
|
||||
current_user.recurly_subscription_id = subscription.uuid
|
||||
if current_user.subscription_trial_ended?
|
||||
current_user.subscription_plan_code = plan_code
|
||||
current_user.subscription_plan_code_set_at = DateTime.now
|
||||
else
|
||||
# we could force a platinum plan since the user has put forward payment already, even in trial
|
||||
puts "user #{current_user.email} is in trial"
|
||||
if plan_code == SubscriptionDefinitions::JAM_PLATINUM || plan_code == SubscriptionDefinitions::JAM_PLATINUM_YEARLY
|
||||
puts "user #{current_user.email} is in trial and buying platinum ; upgrade them already"
|
||||
current_user.subscription_plan_code = plan_code
|
||||
current_user.subscription_plan_code_set_at = DateTime.now
|
||||
else
|
||||
current_user.subscription_plan_code = SubscriptionDefinitions::JAM_GOLD
|
||||
current_user.subscription_plan_code_set_at = DateTime.now
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
current_user.save(validate: false)
|
||||
rescue => e
|
||||
puts "Could not create subscription for user #{current_user.email}. #{e}"
|
||||
return false, subscription, account
|
||||
end
|
||||
|
||||
return true, subscription, account
|
||||
end
|
||||
|
||||
# https://dev.recurly.com/docs/create-subscription
|
||||
def create_subscription(user, plan_code, account)
|
||||
puts "Creating subscription for #{user.email} with plan_code #{plan_code}"
|
||||
subscription = Recurly::Subscription.create(
|
||||
:plan_code => plan_code,
|
||||
:currency => 'USD',
|
||||
:customer_notes => 'Thank you for your business!',
|
||||
:account => {
|
||||
:account_code => account.account_code
|
||||
},
|
||||
:auto_renew => true
|
||||
)
|
||||
def create_subscription(user, plan_code, account, starts_at = nil)
|
||||
|
||||
old_subscription_id = user.recurly_subscription_id
|
||||
|
||||
if old_subscription_id
|
||||
# first, let's try to reactivate it
|
||||
old_subscription = Recurly::Subscription.find(old_subscription_id)
|
||||
begin
|
||||
old_subscription.reactivate
|
||||
puts "reactivated plan! Let's check if it needs changing"
|
||||
if plan_code != old_subscription.plan.plan_code
|
||||
result = old_subscription.update_attributes(
|
||||
:plan_code => plan_code,
|
||||
:timeframe => starts_at.nil? ? 'bill_date' : 'now'
|
||||
)
|
||||
end
|
||||
return old_subscription
|
||||
rescue => e
|
||||
puts "Unable to reactivate/update old plan #{e}"
|
||||
user.update_attribute(:recurly_subscription_id, nil)
|
||||
end
|
||||
end
|
||||
|
||||
if account.billing_info
|
||||
puts "Creating subscription for #{user.email} with plan_code #{plan_code}"
|
||||
subscription = Recurly::Subscription.create(
|
||||
:plan_code => plan_code,
|
||||
:currency => 'USD',
|
||||
:customer_notes => 'Thank you for your business!',
|
||||
:account => {
|
||||
:account_code => account.account_code
|
||||
},
|
||||
:starts_at => starts_at,
|
||||
:auto_renew => true
|
||||
)
|
||||
subscription
|
||||
else
|
||||
puts "User has no billing info; not trying to create a subscription #{user.email}"
|
||||
end
|
||||
subscription
|
||||
end
|
||||
|
||||
def find_subscription(user, account = nil)
|
||||
def find_subscription(user, fed_account = nil)
|
||||
subscription = nil
|
||||
account = nil
|
||||
|
||||
if fed_account.nil?
|
||||
account = get_account(user)
|
||||
else
|
||||
account = fed_account
|
||||
end
|
||||
|
||||
if user.recurly_subscription_id.nil?
|
||||
if account.nil?
|
||||
account = get_account(user)
|
||||
end
|
||||
if account
|
||||
active_subscription = nil
|
||||
account.subscriptions.find_each do |subscription|
|
||||
puts "Subscription: #{subscription.inspect}"
|
||||
puts "Subscription: #{subscription.inspect} #{subscription.state}"
|
||||
if subscription.state == :active || subscription.state == :future
|
||||
active_subscription = subscription
|
||||
break
|
||||
end
|
||||
end
|
||||
subscription = account.subscriptions.first
|
||||
subscription = active_subscription
|
||||
else
|
||||
puts "can't find subscription for account #{account}"
|
||||
end
|
||||
else
|
||||
subscription = Recurly::Subscription.find(user.recurly_subscription_id)
|
||||
begin
|
||||
subscription = Recurly::Subscription.find(user.recurly_subscription_id)
|
||||
rescue Recurly::Resource::NotFound
|
||||
puts "subscription is gone. delete it!"
|
||||
user.update_attribute(:recurly_subscription_id, nil)
|
||||
user.recurly_subscription_id = nil
|
||||
end
|
||||
end
|
||||
|
||||
if user.recurly_subscription_id.nil?
|
||||
if subscription && user.recurly_subscription_id.nil?
|
||||
puts "Repairing subscription ID on account"
|
||||
user.update_attribute(:recurly_subscription_id, subscription.id)
|
||||
user.recurly_subscription_id = subscription.id
|
||||
end
|
||||
|
||||
subscription
|
||||
return [subscription, account]
|
||||
end
|
||||
|
||||
def change_subscription_plan(current_user, plan_code)
|
||||
subscription = find_subscription(current_user)
|
||||
subscription, account = find_subscription(current_user)
|
||||
|
||||
if subscription.nil?
|
||||
puts "no subscription found for user #{current_user.email}"
|
||||
|
|
@ -279,20 +459,133 @@ module JamRuby
|
|||
|
||||
|
||||
def sync_subscription(user)
|
||||
subscription = find_subscription(user)
|
||||
|
||||
if subscription.nil?
|
||||
if user.subscription_plan_code
|
||||
begin
|
||||
# edge case: admin controlled
|
||||
if user.admin_override_plan_code
|
||||
puts "admin controlled plan #{user.email}"
|
||||
user.subscription_plan_code = user.admin_override_plan_code
|
||||
user.subscription_plan_code_set_at = DateTime.now
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.subscription_sync_code = 'admin_control'
|
||||
user.subscription_sync_msg = "admin override - plan_code set to #{user.admin_override_plan_code}"
|
||||
user.save(validate: false)
|
||||
return
|
||||
end
|
||||
|
||||
# edge case: user is in a licensed school
|
||||
if user.has_active_license?
|
||||
puts "user has school license #{user.email}"
|
||||
user.subscription_plan_code = SubscriptionDefinitions::JAM_PLATINUM
|
||||
user.subscription_plan_code_set_at = DateTime.now
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.subscription_sync_code = 'school_license'
|
||||
user.subscription_sync_msg = "has school license - plan_code set to #{SubscriptionDefinitions::JAM_PLATINUM}"
|
||||
user.save(validate: false)
|
||||
return
|
||||
end
|
||||
|
||||
# if user is in trial still, not much book-keeping
|
||||
if !user.subscription_trial_ended?
|
||||
puts "user has a trial still #{user.email}"
|
||||
# there is actually nothing to do, because we don't start billing for any plan until trial is over.
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.subscription_sync_code = 'in_trial'
|
||||
user.subscription_sync_msg = "trial still active - plan_code not altered"
|
||||
user.save(validate: false)
|
||||
return
|
||||
end
|
||||
|
||||
# if the trial has ended, but it was within 2 days of ending the trial date, give the user some leeway
|
||||
if user.subscription_trial_ended? && (user.subscription_trial_ends_at.nil? || (DateTime.now < ( user.subscription_trial_ends_at + 2.days )))
|
||||
puts "user recently ended trial; ignore them #{user.email}"
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.subscription_sync_code = 'trial_recently_ended'
|
||||
user.subscription_sync_msg = "trial ended in past 2 days - plan_code not altered"
|
||||
user.save(validate: false)
|
||||
return
|
||||
end
|
||||
|
||||
# if there is no recurly action here, then they must be coming off of a trial and we have to mark them down
|
||||
if user.recurly_code.nil? && !user.subscription_plan_code.nil?
|
||||
puts "new user #{user.email} has no payment info and is ending their trial"
|
||||
# TODO: send email
|
||||
user.subscription_plan_code = nil
|
||||
user.subscription_plan_code_set_at = DateTime.now
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.subscription_sync_code = 'trial_ended'
|
||||
user.subscription_sync_msg = "trial ended and no subscription set - plan_code set to Free"
|
||||
user.save(validate: false)
|
||||
return
|
||||
end
|
||||
|
||||
account = get_account(user)
|
||||
|
||||
if account.nil?
|
||||
puts "Account is nil? #{user.email}. Strange"
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.save(validate: false)
|
||||
user.subscription_sync_code = 'no_recurly_account'
|
||||
user.subscription_sync_msg = "user has no recurly account - plan_code not altered"
|
||||
return
|
||||
end
|
||||
|
||||
user.is_past_due = account.has_past_due_invoice
|
||||
|
||||
subscription, account = find_subscription(user, account)
|
||||
if subscription
|
||||
user.recurly_subscription_state = subscription.state
|
||||
else
|
||||
user.recurly_subscription_state = nil
|
||||
user.save(validate:false)
|
||||
end
|
||||
else
|
||||
user.recurly_subscription_state = subscription.state
|
||||
if user.subscription_plan_code != subscription.plan.plan_code
|
||||
user.subscription_plan_code = subscription.plan.plan_code
|
||||
|
||||
|
||||
if subscription.nil? || subscription.state == 'expired'
|
||||
puts "user has expired or no plan"
|
||||
user.subscription_plan_code = nil
|
||||
user.subscription_plan_code_set_at = DateTime.now
|
||||
user.subscription_sync_code = 'no_subscription_or_expired'
|
||||
user.subscription_sync_msg = "user has no or expired subscription - plan_code set to Free"
|
||||
else
|
||||
if user.is_past_due
|
||||
if !user.subscription_plan_code.nil?
|
||||
puts "user #{user.email} has a past due plan. We gotta bring them down"
|
||||
user.subscription_plan_code = nil
|
||||
user.subscription_plan_code_set_at = DateTime.now
|
||||
user.subscription_sync_code = 'is_past_due_changed'
|
||||
user.subscription_sync_msg = "payment has gone past due - plan_code set to Free"
|
||||
else
|
||||
puts "user is past due and #{user.email} had no changes"
|
||||
user.subscription_sync_code = 'is_past_due_unchanged'
|
||||
user.subscription_sync_msg = "payment has gone past due, plan_code not altered because already set to free"
|
||||
end
|
||||
else
|
||||
if user.subscription_plan_code != user.desired_plan_code
|
||||
puts "they are back! get them back into their desired plan #{user.email}"
|
||||
user.subscription_plan_code = user.desired_plan_code
|
||||
user.subscription_plan_code_set_at = DateTime.now
|
||||
user.subscription_sync_code = 'good_standing_repaired'
|
||||
user.subscription_sync_msg = "user is in good standing but desired != effective; plan_code set to #{user.desired_plan_code}"
|
||||
else
|
||||
puts "good standing user #{user.email} had no changes"
|
||||
user.subscription_sync_code = 'good_standing_unchanged'
|
||||
user.subscription_sync_msg = "user is in good standing but already set correctly; plan_code not altered"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.save(validate: false)
|
||||
rescue => e
|
||||
puts "Unexpected error in sync_subscription for user #{user.email}"
|
||||
puts e.message
|
||||
user.subscription_last_checked_at = DateTime.now
|
||||
user.subscription_sync_code = 'failed_sync'
|
||||
user.subscription_sync_msg = e.message
|
||||
user.save(validate: false)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def find_or_create_account(current_user, billing_info, recurly_token = nil)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
module JamRuby
|
||||
class SubscriptionDefinitions
|
||||
JAM_SILVER = 'jamsubsilver'
|
||||
JAM_SILVER_WITH_TRIAL = 'jamsubsilvertrial'
|
||||
JAM_SILVER_YEARLY = 'jamsubsilveryearly'
|
||||
JAM_GOLD = 'jamsubgold'
|
||||
JAM_GOLD_WITH_TRIAL = 'jamsubgoldtrial'
|
||||
JAM_GOLD_YEARLY = 'jamsubgoldyearly'
|
||||
JAM_PLATINUM = 'jamsubplatinum'
|
||||
JAM_PLATINUM_WITH_TRIAL = 'jamsubplatinumtrial'
|
||||
JAM_PLATINUM_YEARLY = 'jamsubplatinumyearly'
|
||||
|
||||
# ALL IN HOURS
|
||||
FREE_PLAY_TIME_PER_SESSION = 1
|
||||
|
|
@ -21,10 +21,14 @@ module JamRuby
|
|||
FREE_PLAN = {
|
||||
play_time_per_month: FREE_PLAY_TIME_PER_MONTH,
|
||||
play_time_per_session: FREE_PLAY_TIME_PER_SESSION,
|
||||
recording: false,
|
||||
video: 'no',
|
||||
audio_bitrate: '128',
|
||||
broadcasting: 'no',
|
||||
can_record_audio: false,
|
||||
can_use_video: false,
|
||||
can_record_video: false,
|
||||
can_record_wave: false,
|
||||
video_resolution: 0,
|
||||
audio_max_bitrate: 1, # 128
|
||||
can_broadcast: false,
|
||||
broadcasting_type: 3,
|
||||
max_players: 4
|
||||
|
||||
}
|
||||
|
|
@ -32,41 +36,53 @@ module JamRuby
|
|||
SILVER_PLAN = {
|
||||
play_time_per_month: SILVER_PLAY_TIME_PER_MONTH,
|
||||
play_time_per_session: SILVER_PLAY_TIME_PER_SESSION,
|
||||
recording: false,
|
||||
video: 'cif',
|
||||
audio_bitrate: '192',
|
||||
broadcasting: 'free',
|
||||
can_record_audio: false,
|
||||
can_record_video: false,
|
||||
can_use_video: true,
|
||||
can_record_wave: true,
|
||||
video_resolution: 1, # CIF in the backend
|
||||
audio_max_bitrate: 2, #192
|
||||
can_broadcast: true,
|
||||
broadcasting_type: 3,
|
||||
max_players: 6
|
||||
}
|
||||
|
||||
GOLD_PLAN = {
|
||||
play_time_per_month: GOLD_PLAY_TIME_PER_MONTH,
|
||||
play_time_per_session: GOLD_PLAY_TIME_PER_SESSION,
|
||||
recording: true,
|
||||
video: '720p',
|
||||
audio_bitrate: '256',
|
||||
broadcasting: 'free',
|
||||
can_record_audio: true,
|
||||
can_record_video: true,
|
||||
can_use_video: true,
|
||||
can_record_wave: true,
|
||||
video_resolution: 3, # 720p in the backend
|
||||
audio_max_bitrate: 3, #256
|
||||
can_broadcast: true,
|
||||
broadcasting_type: 3,
|
||||
max_players: nil
|
||||
}
|
||||
|
||||
PLATINUM_PLAN = {
|
||||
play_time_per_month: PLATINUM_PLAY_TIME_PER_MONTH,
|
||||
play_time_per_session: PLATINUM_PLAY_TIME_PER_SESSION,
|
||||
recording: true,
|
||||
video: '1080p',
|
||||
audio_bitrate: '512',
|
||||
broadcasting: 'busking',
|
||||
can_record_audio: true,
|
||||
can_record_video: true,
|
||||
can_use_video: true,
|
||||
can_record_wave: true,
|
||||
video_resolution: 4, # 1080p in the backend
|
||||
audio_max_bitrate: 5, #512
|
||||
can_broadcast: true,
|
||||
broadcasting_type: 3,
|
||||
max_players: nil
|
||||
}
|
||||
|
||||
def self.rules(plan_code)
|
||||
if plan_code == nil
|
||||
FREE_PLAN
|
||||
elsif plan_code == JAM_SILVER || plan_code == JAM_SILVER_WITH_TRIAL
|
||||
elsif plan_code == JAM_SILVER || plan_code == JAM_SILVER_YEARLY
|
||||
SILVER_PLAN
|
||||
elsif plan_code == JAM_GOLD || plan_code == JAM_GOLD_WITH_TRIAL
|
||||
elsif plan_code == JAM_GOLD || plan_code == JAM_GOLD_YEARLY
|
||||
GOLD_PLAN
|
||||
elsif plan_code == JAM_PLATINUM || plan_code == JAM_PLATINUM_WITH_TRIAL
|
||||
elsif plan_code == JAM_PLATINUM || plan_code == JAM_PLATINUM_YEARLY
|
||||
PLATINUM_PLAN
|
||||
else
|
||||
raise "unknown plan #{plan_code}"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
require 'spec_helper'
|
||||
|
||||
|
||||
describe "User Subscriptions" do
|
||||
|
||||
let(:user1) {FactoryGirl.create(:user)}
|
||||
let(:client) { RecurlyClient.new }
|
||||
before(:each) do
|
||||
|
||||
end
|
||||
|
||||
it "empty results" do
|
||||
user1.touch
|
||||
|
||||
User.subscription_sync
|
||||
|
||||
user1.reload
|
||||
|
||||
user1.subscription_sync_code.should be_nil
|
||||
user1.subscription_last_checked_at.should be_nil
|
||||
end
|
||||
|
||||
it "user not in trial" do
|
||||
user1.subscription_plan_code = SubscriptionDefinitions::JAM_PLATINUM
|
||||
user1.subscription_trial_ends_at = 1.days.ago
|
||||
|
||||
client.sync_subscription(user1)
|
||||
user1.reload
|
||||
user1.subscription_sync_code.should == "trial_recently_ended"
|
||||
user1.subscription_last_checked_at.should_not be_nil
|
||||
user1.subscription_plan_code.should == SubscriptionDefinitions::JAM_PLATINUM
|
||||
|
||||
user1.subscription_trial_ends_at = 3.days.ago
|
||||
user1.subscription_last_checked_at = 2.days.ago
|
||||
user1.save!
|
||||
|
||||
User.subscription_sync
|
||||
|
||||
user1.reload
|
||||
|
||||
user1.subscription_sync_code.should == "trial_ended"
|
||||
user1.subscription_last_checked_at.should_not be_nil
|
||||
user1.subscription_plan_code.should be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -680,16 +680,31 @@
|
|||
// browser: use session cookie, and auth with token
|
||||
// native: use session cookie, and use the token
|
||||
// latency_tester: ask for client ID from backend; no token (trusted)
|
||||
|
||||
|
||||
if (isClientMode()) {
|
||||
var client_type = context.JK.clientType()
|
||||
var client_id = (gon.global.env == "development" ? $.cookie('client_id') : null)
|
||||
var machine = context.jamClient.SessionGetMacHash()
|
||||
if (machine) {
|
||||
machine = machine.all
|
||||
}
|
||||
}
|
||||
else {
|
||||
var client_type = 'latency_tester'
|
||||
var client_id = context.jamClient.clientID
|
||||
var machine = null
|
||||
}
|
||||
var params = {
|
||||
channel_id: channelId,
|
||||
token: rememberToken,
|
||||
client_type: isClientMode() ? context.JK.clientType() : 'latency_tester',
|
||||
client_id: isClientMode() ? (gon.global.env == "development" ? $.cookie('client_id') : null): context.jamClient.clientID,
|
||||
client_type: client_type, // isClientMode() ? context.JK.clientType() : 'latency_tester',
|
||||
client_id: client_id,
|
||||
machine: machine,
|
||||
os: context.JK.GetOSAsString(),
|
||||
//jamblaster_serial_no: context.PlatformStore.jamblasterSerialNo(),
|
||||
udp_reachable: context.JK.StunInstance ? !context.JK.StunInstance.sync() : null // latency tester doesn't have the stun class loaded
|
||||
}
|
||||
|
||||
var uri = context.gon.websocket_gateway_uri + '?' + $.param(params); // Set in index.html.erb.
|
||||
|
||||
logger.debug("connecting websocket: " + uri);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
function applySubscriptionPolicy() {
|
||||
|
||||
}
|
||||
function OpenSystemBrowser(href) {
|
||||
dbg("OpenSystemBrowser('" + href + "')");
|
||||
context.window.open(href);
|
||||
|
|
@ -1378,6 +1381,10 @@
|
|||
function UserAttention(option) {
|
||||
}
|
||||
|
||||
function SessionGetMacHash() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function IsFrontendVisible() {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1733,6 +1740,7 @@
|
|||
// Websocket/Auth sessions
|
||||
this.OnLoggedIn = OnLoggedIn;
|
||||
this.OnLoggedOut = OnLoggedOut;
|
||||
this.SessionGetMacHash = SessionGetMacHash;
|
||||
|
||||
this.UserAttention = UserAttention;
|
||||
this.IsFrontendVisible = IsFrontendVisible;
|
||||
|
|
@ -1801,6 +1809,7 @@
|
|||
this.getPluginList = getPluginList;
|
||||
this.clearPluginList = clearPluginList;
|
||||
this.listTrackAssignments = listTrackAssignments;
|
||||
this.applySubscriptionPolicy = applySubscriptionPolicy;
|
||||
this.clientID = "devtester";
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2813,6 +2813,17 @@
|
|||
})
|
||||
}
|
||||
|
||||
function updatePayment(options) {
|
||||
options = options || {}
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/recurly/update_payment',
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function getSubscription() {
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
|
|
@ -2822,8 +2833,8 @@
|
|||
})
|
||||
}
|
||||
|
||||
function changeSubscription(options) {
|
||||
options = options || {}
|
||||
function changeSubscription(plan_code) {
|
||||
var options = {plan_code: plan_code}
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/recurly/change_subscription',
|
||||
|
|
@ -2844,6 +2855,15 @@
|
|||
})
|
||||
}
|
||||
|
||||
function listInvoices(options) {
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: '/api/recurly/invoice_history',
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
})
|
||||
}
|
||||
|
||||
function createLiveStream(musicSessionId) {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
|
|
@ -3171,9 +3191,11 @@
|
|||
this.findPublicSessions = findPublicSessions;
|
||||
this.getConfigClient = getConfigClient;
|
||||
this.createSubscription = createSubscription;
|
||||
this.updatePayment = updatePayment;
|
||||
this.getSubscription = getSubscription;
|
||||
this.changeSubscription = changeSubscription;
|
||||
this.cancelSubscription= cancelSubscription;
|
||||
this.listInvoices = listInvoices;
|
||||
return this;
|
||||
};
|
||||
})(window, jQuery);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
var value = $(this).val()
|
||||
enable_disable_instruments(value == "true")
|
||||
})
|
||||
|
||||
// jam_ruby_user[desired_plan_code]
|
||||
}
|
||||
|
||||
// register form elements relating to location to update appropriately as the user makes changes
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
logger = context.JK.logger
|
||||
|
||||
LocationActions = context.LocationActions
|
||||
AppStore = context.AppStore
|
||||
UserStore = context.UserStore
|
||||
SubscriptionActions = context.SubscriptionActions
|
||||
|
||||
|
||||
profileUtils = context.JK.ProfileUtils
|
||||
|
||||
|
|
@ -12,7 +14,9 @@ profileUtils = context.JK.ProfileUtils
|
|||
mixins: [
|
||||
ICheckMixin,
|
||||
Reflux.listenTo(AppStore, "onAppInit"),
|
||||
Reflux.listenTo(UserStore, "onUserChanged")
|
||||
Reflux.listenTo(UserStore, "onUserChanged"),
|
||||
Reflux.listenTo(@LocationStore, "onLocationsChanged"),
|
||||
Reflux.listenTo(SubscriptionStore, "onSubscriptionChanged")
|
||||
]
|
||||
|
||||
shownOnce: false
|
||||
|
|
@ -47,25 +51,26 @@ profileUtils = context.JK.ProfileUtils
|
|||
|
||||
|
||||
componentDidMount: () ->
|
||||
@checkboxes = [{selector: 'input.billing-address-in-us', stateKey: 'billingInUS'}]
|
||||
#@checkboxes = [{selector: 'input.billing-address-in-us', stateKey: 'billingInUS'}]
|
||||
|
||||
@root = $(@getDOMNode())
|
||||
@endOfList = @root.find('.end-of-payments-list')
|
||||
@contentBodyScroller = @root
|
||||
@iCheckify()
|
||||
#@iCheckify()
|
||||
@preparePaymentMethod()
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
@iCheckify()
|
||||
#@iCheckify()
|
||||
|
||||
$expiration = @root.find('input.expiration')
|
||||
if !$expiration.data('payment-applied')
|
||||
$expiration.payment('formatCardExpiry').data('payment-applied', true)
|
||||
$cardNumber = @root.find("input.card-number")
|
||||
if !$cardNumber.data('payment-applied')
|
||||
$cardNumber.payment('formatCardNumber').data('payment-applied', true)
|
||||
$cvv = @root.find("input.cvv")
|
||||
if !$cvv.data('payment-applied')
|
||||
$cvv.payment('formatCardCVC').data('payment-applied', true)
|
||||
#$expiration = @root.find('input.expiration')
|
||||
#if !$expiration.data('payment-applied')
|
||||
# $expiration.payment('formatCardExpiry').data('payment-applied', true)
|
||||
#$cardNumber = @root.find("input.card-number")
|
||||
#if !$cardNumber.data('payment-applied')
|
||||
# $cardNumber.payment('formatCardNumber').data('payment-applied', true)
|
||||
#$cvv = @root.find("input.cvv")
|
||||
#if !$cvv.data('payment-applied')
|
||||
# $cvv.payment('formatCardCVC').data('payment-applied', true)
|
||||
|
||||
if @currentNext() == null
|
||||
@contentBodyScroller.off('scroll')
|
||||
|
|
@ -81,6 +86,8 @@ profileUtils = context.JK.ProfileUtils
|
|||
if @activeTile(prevState.selected) != @activeTile() && @getCurrentList().length == 0
|
||||
@refresh()
|
||||
|
||||
@configureElements()
|
||||
|
||||
|
||||
registerInfiniteScroll:() ->
|
||||
$scroller = @contentBodyScroller
|
||||
|
|
@ -119,10 +126,10 @@ profileUtils = context.JK.ProfileUtils
|
|||
@clearResults()
|
||||
@screenVisible = true
|
||||
@refresh()
|
||||
@getUncollectables()
|
||||
#@getUncollectables()
|
||||
|
||||
resetErrors: () ->
|
||||
@setState({ccError: null, cvvError: null, expiryError: null, billingInUSError: null, zipCodeError: null, nameError: null})
|
||||
@setState({ccError: null, cvvError: null, expiryError: null, billingInUSError: null, zipCodeError: null, nameError: null, firstNameError: null, lastNameError: null, cityError: null, stateError: null, address1Error: null, countryError: null, address2Error:null})
|
||||
|
||||
onBack: (e) ->
|
||||
e.preventDefault()
|
||||
|
|
@ -140,14 +147,24 @@ profileUtils = context.JK.ProfileUtils
|
|||
@refreshTeacherDistributions()
|
||||
else if @activeTile() == @TILE_PAYMENTS_TO_JAMKAZAM
|
||||
@refreshSales()
|
||||
else if @activeTile() == @TILE_PAYMENT_METHOD
|
||||
@refreshPayment()
|
||||
else
|
||||
logger.debug("dropping refresh because no tile match", @activeTile)
|
||||
|
||||
refreshPayment:() ->
|
||||
SubscriptionActions.updateSubscription()
|
||||
|
||||
refreshSales: () ->
|
||||
@refreshing = true
|
||||
rest.getSalesHistory(@currentQuery)
|
||||
.done(@salesHistoryDone)
|
||||
.fail(@salesHistoryFail)
|
||||
#rest.getSalesHistory(@currentQuery)
|
||||
#.done(@salesHistoryDone)
|
||||
#.fail(@salesHistoryFail)
|
||||
|
||||
|
||||
rest.listInvoices()
|
||||
.done(@paymentHistoryDone)
|
||||
.fail(@paymentHistoryFail)
|
||||
|
||||
refreshTeacherDistributions: () ->
|
||||
@refreshing = true
|
||||
|
|
@ -168,6 +185,14 @@ profileUtils = context.JK.ProfileUtils
|
|||
@refreshing = false
|
||||
@app.notifyServerError jqXHR, 'Payments to JamKazam Unavailable'
|
||||
|
||||
paymentHistoryDone:(response) ->
|
||||
@refreshing = false
|
||||
this.setState({salesNext: null, sales: this.state.sales.concat(response.entries), pastDue: response.past_due})
|
||||
|
||||
paymentHistoryFail:(jqXHR) ->
|
||||
@refreshing = false
|
||||
@app.notifyServerError jqXHR, 'Payments to JamKazam Unavailable'
|
||||
|
||||
teacherDistributionsDone:(response) ->
|
||||
@refreshing = false
|
||||
this.setState({distributionsNext: response.next, distributions: this.state.distributions.concat(response.entries)})
|
||||
|
|
@ -183,7 +208,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
@app.notifyServerError jqXHR, 'Unable to fetch uncollectable info'
|
||||
|
||||
clearResults:() ->
|
||||
this.setState({salesCurrentPage: 0, sales: [], distributionsCurrentPage: 0, distributions: [], salesNext: null, distributionsNext: null, updating: false})
|
||||
this.setState({salesCurrentPage: 0, sales: [], distributionsCurrentPage: 0, distributions: [], salesNext: null, distributionsNext: null, updating: false, pastDue: false})
|
||||
|
||||
buildQuery:(page = @getCurrentPage()) ->
|
||||
@currentQuery = this.defaultQuery(page)
|
||||
|
|
@ -245,10 +270,11 @@ profileUtils = context.JK.ProfileUtils
|
|||
distributionsNext: null
|
||||
sales: [],
|
||||
distributions: []
|
||||
selected: 'payments to jamkazam',
|
||||
selected: @TILE_PAYMENT_METHOD,
|
||||
updating: false,
|
||||
billingInUS: true,
|
||||
userWantsUpdateCC: false,
|
||||
selectedCountry: null,
|
||||
uncollectables: []
|
||||
}
|
||||
|
||||
|
|
@ -334,6 +360,154 @@ profileUtils = context.JK.ProfileUtils
|
|||
<br className="clearall" />
|
||||
</div>`
|
||||
|
||||
|
||||
onSubscriptionChanged: (subscription) ->
|
||||
@setState({pastDue: subscription.past_due})
|
||||
|
||||
onLocationsChanged: (countries) ->
|
||||
console.log("countries in ", countries)
|
||||
@setState({countries: countries})
|
||||
|
||||
onCountryChanged: (e) ->
|
||||
val = $(e.target).val()
|
||||
@setState({selectedCountry: val})
|
||||
|
||||
currentCountry: () ->
|
||||
this.state.selectedCountry || this.props.selectedCountry || ''
|
||||
|
||||
|
||||
openBrowser: () ->
|
||||
context.JK.popExternalLink("https://www.jamkazam.com/client#/subscription")
|
||||
|
||||
onRecurlyToken: (err, token_data) ->
|
||||
|
||||
if err
|
||||
console.log("error", err)
|
||||
|
||||
handled = false
|
||||
if err.code == "validation" && err.fields?
|
||||
handled = true
|
||||
for field in err.fields
|
||||
console.log("problem field", field)
|
||||
# handle error using err.code and err.fields
|
||||
if field == "first_name"
|
||||
@setState({firstNameError: true})
|
||||
if field == "last_name"
|
||||
@setState({lastNameError: true})
|
||||
if err.code == "invalid-parameter"
|
||||
if err.fields.indexOf("year") > -1 || err.fields.indexOf("month") > -1 || err.fields.indexOf("number") > -1 || err.fields.indexOf("cvv") > -1
|
||||
@setState({ccError: true})
|
||||
handled = true
|
||||
|
||||
@app.layout.notify({title: 'Please double-check ' + err.fields[0], text: err.message})
|
||||
if !handled
|
||||
@app.layout.notify({title: "Error Updating Payment Info", text: JSON.stringify(err)})
|
||||
@setState({updating: false})
|
||||
else
|
||||
# recurly.js has filled in the 'token' field, so now we can submit the
|
||||
# form to your server
|
||||
console.log("eintercepted", token_data)
|
||||
rest.updatePayment({recurly_token: token_data.id}).done((response) => @updatePaymentDone(response)).fail((jqXHR) => @updatePaymentFailure(jqXHR))
|
||||
|
||||
updatePaymentDone: (response) ->
|
||||
@setState({updating: false})
|
||||
|
||||
logger.debug("recurly submitted: " + JSON.stringify(response))
|
||||
|
||||
@setState({userWantsUpdateCC: false})
|
||||
|
||||
#if @state.shouldShowName
|
||||
window.UserActions.refresh()
|
||||
|
||||
if response.uncollectables
|
||||
context.JK.Banner.showAlert('Credit Card Updated', 'Than you. Your credit card info has been updated.<br/><br/>We will try to bill any unpaid lessons within the next hour, and an email will be sent at that time.')
|
||||
else
|
||||
@app.layout.notify({title: 'Payment Updated', text: 'Your payment info has been updated.'})
|
||||
|
||||
updatePaymentFailure: (jqXHR) ->
|
||||
@setState({updating: false})
|
||||
handled = false
|
||||
if jqXHR.status == 404
|
||||
errors = JSON.parse(jqXHR.responseText)?.message
|
||||
@app.layout.notify({title: "Error Updating Payment Info", text: errors})
|
||||
else
|
||||
@app.notifyServerError(jqXHR, 'Payment Not Updated')
|
||||
|
||||
onFormSubmit: (event) ->
|
||||
form = event.target
|
||||
console.log("ok work this", form)
|
||||
event.preventDefault()
|
||||
@setState({updating: true})
|
||||
recurly.token(@elements, form, @onRecurlyToken)
|
||||
|
||||
configureRecurly: () ->
|
||||
unless window.configuredRecurly
|
||||
console.log("configuring recurly...")
|
||||
window.recurly.configure(gon.global.recurly_public_api_key)
|
||||
window.configuredRecurly = true
|
||||
@elements = window.recurly.Elements()
|
||||
|
||||
delayedConfigure: () ->
|
||||
if !window.recurly?
|
||||
console.log("relaunch delayed recurly configure")
|
||||
setTimeout(() =>
|
||||
@delayedConfigure()
|
||||
, 1000)
|
||||
return
|
||||
|
||||
@configureRecurly()
|
||||
@configureElements()
|
||||
|
||||
configureElements: () ->
|
||||
|
||||
node = $('#subscription-elements')
|
||||
if node.length > 0
|
||||
if window.recurly && @elements? && !node.data('recurlied')
|
||||
commonStyles = {
|
||||
inputType: 'mobileSelect',
|
||||
style: {
|
||||
fontSize:'1em'
|
||||
color:'black',
|
||||
placeholder: {
|
||||
color:'black',
|
||||
}
|
||||
invalid: {
|
||||
fontColor: 'red'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#cardNumberStyle = $.extend(true, {}, commonStyles, {style:{placeholder:{content: 'Card Number'}}})
|
||||
#cardMonthStyle = $.extend(true, {}, commonStyles, {style:{placeholder:{content: 'MM'}}})
|
||||
#cardYearStyle = $.extend(true, {}, commonStyles, {style:{placeholder:{content: 'YYYY'}}})
|
||||
#cardCvcStyle = $.extend(true, {}, commonStyles, {style:{placeholder:{content: 'CVC'}}})
|
||||
|
||||
#cardNumberElement = @elements.CardNumberElement( cardNumberStyle )
|
||||
#cardMonthElement = @elements.CardMonthElement(cardMonthStyle)
|
||||
#cardYearElement = @elements.CardYearElement(cardYearStyle)
|
||||
#cardCvvElement = @elements.CardCvvElement(cardCvcStyle)
|
||||
|
||||
#cardNumberElement.attach('#subscription-elements-number')
|
||||
#cardMonthElement.attach('#subscription-elements-month')
|
||||
#cardYearElement.attach('#subscription-elements-year')
|
||||
#cardCvvElement.attach('#subscription-elements-cvv')
|
||||
|
||||
cardElement = @elements.CardElement(commonStyles)
|
||||
cardElement.attach("#subscription-elements")
|
||||
|
||||
document.querySelector('#user-payment-submit').addEventListener('submit', @onSubmit.bind(this))
|
||||
node.data('recurlied', true)
|
||||
|
||||
preparePaymentMethod: () ->
|
||||
LocationActions.load()
|
||||
|
||||
setTimeout(() =>
|
||||
@delayedConfigure()
|
||||
, 200)
|
||||
|
||||
defaultText: () ->
|
||||
'Select Country'
|
||||
|
||||
paymentMethod: () ->
|
||||
disabled = @state.updating || @reuseStoredCard()
|
||||
|
||||
|
|
@ -347,14 +521,37 @@ profileUtils = context.JK.ProfileUtils
|
|||
inUSClasses = {field: true, "billing-in-us": true, error: @state.billingInUSError}
|
||||
zipCodeClasses = {field: true, "zip-code": true, error: @state.zipCodeError}
|
||||
nameClasses= {field: true, "name": true, error: @state.nameError}
|
||||
firstNameClasses= {field: true, "first-name": true, error: @state.firstNameError}
|
||||
lastNameClasses= {field: true, "last-name": true, error: @state.lastNameError}
|
||||
address1Classes= {field: true, "address-1": true, error: @state.address1Error}
|
||||
address2Classes= {field: true, "address-2": true, error: @state.address2Error}
|
||||
cityClasses= {field: true, "city": true, error: @state.cityError}
|
||||
stateClasses= {field: true, "state": true, error: @state.stateError}
|
||||
countryClasses = {field: true, "country": true, error: @state.countryError}
|
||||
|
||||
formClasses= {stored: @reuseStoredCard()}
|
||||
leftColumnClasses = {column: true, 'column-left': true, stored: @reuseStoredCard()}
|
||||
rightColumnClasses = {column: true, 'column-right': true, stored: @reuseStoredCard()}
|
||||
|
||||
if @state.countries?
|
||||
countries = [`<option key="" value="">{this.defaultText()}</option>`]
|
||||
for countryId, countryInfo of @state.countries
|
||||
countries.push(`<option key={countryId} value={countryId}>{countryInfo.name}</option>`)
|
||||
|
||||
country = @state.countries[this.currentCountry()]
|
||||
else
|
||||
countries = []
|
||||
|
||||
countryJsx = `
|
||||
<select disabled={disabled} name="countries" onChange={this.onCountryChanged} value={this.currentCountry()} data-recurly="country" autoComplete="shipping country" className="country">{countries}</select>`
|
||||
|
||||
|
||||
if @state.uncollectables.length > 0
|
||||
uncollectable = @state.uncollectables[0]
|
||||
uncollectableMessage = `<div className="uncollectable-msg">A charge for your music lesson with {uncollectable.teacher.name} failed. Please update your credit card information immediately so that we can pay the instructor. If you have called your credit card provider and believe there should be no problem with your card, please email us at <a href="mailto:support@jamkazam.com">support@jamkazam.com</a> so that we can figure out what's gone wrong. Thank you!</div>`
|
||||
|
||||
if @state.pastDue
|
||||
uncollectableMessage = `<div className="uncollectable-msg">A charge for your subscription has failed. Please update your credit card information immediately so that you can restore your plan.</div>`
|
||||
if @state.user?['has_stored_credit_card?'] && @state.uncollectables.length == 0
|
||||
if @state.userWantsUpdateCC
|
||||
header = 'Please update your billing address and payment information below.'
|
||||
|
|
@ -362,60 +559,73 @@ profileUtils = context.JK.ProfileUtils
|
|||
actions = `<div className="actions">
|
||||
<a className={classNames(backClassNames)} onClick={this.onBack}>BACK</a>
|
||||
{updateCardAction}
|
||||
<a className={classNames(submitClassNames)} onClick={this.onSubmit}>SUBMIT CARD INFORMATION</a>
|
||||
<a className={classNames(submitClassNames)} onClick={this.onSubmitForm}>SUBMIT CARD INFORMATION</a>
|
||||
</div>`
|
||||
else
|
||||
header = 'You have already entered a credit card in JamKazam.'
|
||||
header = 'You have have a payment method on file already.'
|
||||
updateCardAction = `<a className={classNames(updateCardClassNames)} onClick={this.onUnlockPaymentInfo}>I'D LIKE TO UPDATE MY PAYMENT INFO</a>`
|
||||
managedSubscriptionAction = `<a href="/client#/account/subscription" className="button-orange">MANAGE MY SUBSCRIPTION</a>`
|
||||
actions = `<div className="actions">
|
||||
<a className={classNames(backClassNames)} onClick={this.onBack}>BACK</a>
|
||||
{updateCardAction}
|
||||
{managedSubscriptionAction}
|
||||
</div>`
|
||||
else
|
||||
header = 'Please enter your billing address and payment information below.'
|
||||
header = `<div>Please enter your billing address and payment information below.<br/><br/><span class="no-storage-note">This information is sent to <a href="https://recurly.com/customers/" rel="external">Recurly</a>, which is compliant with the PCI-DSS security standard. JamKazam does not receive or store your credit card.</span></div>`
|
||||
actions = `<div className="actions">
|
||||
<a className={classNames(backClassNames)} onClick={this.onBack}>BACK</a><a
|
||||
className={classNames(submitClassNames)} onClick={this.onSubmit}>SUBMIT CARD INFORMATION</a>
|
||||
className={classNames(submitClassNames)} onClick={this.onSubmitForm}>SUBMIT CARD INFORMATION</a>
|
||||
</div>`
|
||||
|
||||
firstNameField =
|
||||
`<div className={classNames(firstNameClasses)}>
|
||||
<label>First Name:</label>
|
||||
<input disabled={disabled} type="text" name="first_name" className="first_name" data-recurly="first_name" required autoComplete="cc-given-name"></input>
|
||||
</div>`
|
||||
lastNameField =
|
||||
`<div className={classNames(lastNameClasses)}>
|
||||
<label>Last Name:</label>
|
||||
<input disabled={disabled} type="text" name="last_name" className="last_name" data-recurly="last_name" required autoComplete="cc-surname"></input>
|
||||
</div>`
|
||||
if @state.shouldShowName && @state.user?.name?
|
||||
username = @state.user?.name
|
||||
nameField =
|
||||
`<div className={classNames(nameClasses)}>
|
||||
<label>Name:</label>
|
||||
<input id="set-user-on-card" disabled={disabled} type="text" name="name" className="name" defaultValue={username}></input>
|
||||
</div>`
|
||||
|
||||
`<div>
|
||||
<div className={classNames(leftColumnClasses)}>
|
||||
{uncollectableMessage}
|
||||
<div className="paymethod-header">{header}</div>
|
||||
<form autoComplete="on" onSubmit={this.onSubmit} className={classNames(formClasses)}>
|
||||
{nameField}
|
||||
<div className={classNames(cardNumberFieldClasses)}>
|
||||
<label>Card Number:</label>
|
||||
<input placeholder="1234 5678 9123 4567" type="tel" autoComplete="cc-number" disabled={disabled}
|
||||
type="text" name="card-number" className="card-number"></input>
|
||||
<form autoComplete="on" onSubmit={this.onSubmit} className={classNames(formClasses)} id="user-payment-submit">
|
||||
{firstNameField}
|
||||
{lastNameField}
|
||||
<div className={classNames(address1Classes)}>
|
||||
<label>Address 1:</label>
|
||||
<input type="text" data-recurly="address1" disabled={disabled} required autoComplete="shipping address-line1" className="address-1"></input>
|
||||
</div>
|
||||
<div className={classNames(expirationFieldClasses)}>
|
||||
<label>Expiration Date:</label>
|
||||
<input placeholder="MM / YY" autoComplete="cc-expiry" disabled={disabled} type="text" name="expiration"
|
||||
className="expiration"></input>
|
||||
<div className={classNames(address2Classes)}>
|
||||
<label>Address 2:</label>
|
||||
<input type="text" data-recurly="address2" disabled={disabled} autoComplete="shipping address-line2" className="address-2"></input>
|
||||
</div>
|
||||
<div className={classNames(cvvFieldClasses)}>
|
||||
<label>CVV:</label>
|
||||
<input autoComplete="off" disabled={disabled} type="text" name="cvv" className="cvv"></input>
|
||||
<div className={classNames(cityClasses)}>
|
||||
<label for="city">City:</label>
|
||||
<input type="text" data-recurly="city" className="city" disabled={disabled} required autoComplete="shipping address-level2"></input>
|
||||
</div>
|
||||
<div className={classNames(stateClasses)}>
|
||||
<label for="state">State:</label>
|
||||
<input type="text" data-recurly="state" className="state" disabled={disabled} required autoComplete="shipping address-level1"></input>
|
||||
</div>
|
||||
<div className={classNames(zipCodeClasses)}>
|
||||
<label>Zip Code</label>
|
||||
<input autoComplete="off" disabled={disabled || !this.state.billingInUS} type="text" name="zip"
|
||||
<label>Postal Code:</label>
|
||||
<input type="text" name="zip" data-recurly="postal_code" disabled={disabled} required autoComplete="shipping postal-code"
|
||||
className="zip"></input>
|
||||
</div>
|
||||
<div className={classNames(inUSClasses)}>
|
||||
<label>Billing Address<br/>is in the U.S.</label>
|
||||
<input type="checkbox" name="billing-address-in-us" className="billing-address-in-us"
|
||||
value={this.state.billingInUS}/>
|
||||
<div className={classNames(countryClasses)}>
|
||||
<label for="country">Country:</label>
|
||||
{countryJsx}
|
||||
</div>
|
||||
<input style={{'display':'none'}} type="submit" name="submit"/>
|
||||
<div className={classNames(cardNumberFieldClasses)}>
|
||||
<label>Card:</label>
|
||||
<span className="recurly-holder" id="subscription-elements"></span>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="recurly-token" data-recurly="token"></input>
|
||||
</form>
|
||||
{actions}
|
||||
</div>
|
||||
|
|
@ -458,7 +668,6 @@ profileUtils = context.JK.ProfileUtils
|
|||
<thead>
|
||||
<tr>
|
||||
<th>CHARGED AT</th>
|
||||
<th>METHOD</th>
|
||||
<th>DESCRIPTION</th>
|
||||
<th>REASON</th>
|
||||
<th>AMOUNT</th>
|
||||
|
|
@ -484,19 +693,24 @@ profileUtils = context.JK.ProfileUtils
|
|||
description = items.join(', ')
|
||||
else
|
||||
# this is a recurly webhook
|
||||
transaction = paymentHistory.transaction
|
||||
amt = transaction.amount_in_cents
|
||||
status = transaction.transaction_type
|
||||
#transaction = paymentHistory.transaction
|
||||
#amt = transaction.amount_in_cents
|
||||
#status = transaction.transaction_type
|
||||
#displayAmount = '($' + (amt/100).toFixed(2) + ')'
|
||||
#date = context.JK.formatDate(transaction.transaction_at, true)
|
||||
#description = transaction.admin_description
|
||||
invoice = paymentHistory
|
||||
amt = invoice.total_in_cents
|
||||
status = invoice.state
|
||||
displayAmount = '($' + (amt/100).toFixed(2) + ')'
|
||||
date = context.JK.formatDate(transaction.transaction_at, true)
|
||||
description = transaction.admin_description
|
||||
date = context.JK.formatDate(invoice.created_at, true)
|
||||
description = invoice.description
|
||||
|
||||
amountClasses = {status: status}
|
||||
|
||||
row =
|
||||
`<tr>
|
||||
<td>{date}</td>
|
||||
<td className="capitalize">{paymentMethod}</td>
|
||||
<td>{description}</td>
|
||||
<td className="capitalize">{status}</td>
|
||||
<td className={classNames(amountClasses)}>{displayAmount}</td>
|
||||
|
|
@ -509,7 +723,6 @@ profileUtils = context.JK.ProfileUtils
|
|||
<thead>
|
||||
<tr>
|
||||
<th>DATE</th>
|
||||
<th>METHOD</th>
|
||||
<th>DESCRIPTION</th>
|
||||
<th>STATUS</th>
|
||||
<th>AMOUNT</th>
|
||||
|
|
@ -530,7 +743,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
|
||||
selectionMade: (selection, e) ->
|
||||
e.preventDefault()
|
||||
@getUncollectables()
|
||||
#@getUncollectables()
|
||||
@setState({selected: selection})
|
||||
|
||||
activeTile: (selected = this.state.selected) ->
|
||||
|
|
@ -594,7 +807,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
|
||||
`<div className="content-body-scroller">
|
||||
<div className="profile-header profile-head">
|
||||
<div className="account-header">payment<br/>history:</div>
|
||||
<div className="account-header">payment<br/>management:</div>
|
||||
{profileNav}
|
||||
<div className="clearall"></div>
|
||||
</div>
|
||||
|
|
@ -621,7 +834,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
scheduling_communication = 'teacher'
|
||||
correspondence_email = @root.find('input[name="correspondence_email"]').val()
|
||||
|
||||
@setState(updating: true)
|
||||
@setState({updating: true})
|
||||
rest.updateSchool({
|
||||
id: this.state.school.id,
|
||||
name: name,
|
||||
|
|
@ -634,6 +847,52 @@ profileUtils = context.JK.ProfileUtils
|
|||
|
||||
@app.layout.notify({title: "update success", text: "Your school information has been successfully updated"})
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
form = document.querySelector('#user-payment-submit')
|
||||
e.preventDefault()
|
||||
@onSubmit(form)
|
||||
|
||||
onSubmit: (form) ->
|
||||
@resetErrors()
|
||||
#e.preventDefault()
|
||||
console.log("onSubmit")
|
||||
if !window.recurly?
|
||||
@app.layout.notify({
|
||||
title: 'Payment System Not Loaded',
|
||||
text: "Please refresh this page and try to enter your info again. Sorry for the inconvenience!"
|
||||
})
|
||||
else
|
||||
errored = false
|
||||
# do a client-side sweep 1st
|
||||
if !@root.find('input.first_name').val()
|
||||
errored = true
|
||||
@setState({firstNameError: true})
|
||||
if !@root.find('input.last_name').val()
|
||||
errored = true
|
||||
@setState({lastNameError: true})
|
||||
if !@root.find('input.address-1').val()
|
||||
errored = true
|
||||
@setState({address1Error: true})
|
||||
if !@root.find('input.city').val()
|
||||
errored = true
|
||||
@setState({cityError: true})
|
||||
if !@root.find('input.state').val()
|
||||
errored = true
|
||||
@setState({stateError: true})
|
||||
if !@root.find('select.country').val()
|
||||
errored = true
|
||||
@setState({countryError: true})
|
||||
if !@root.find('input.zip').val()
|
||||
errored = true
|
||||
@setState({zipCodeError: true})
|
||||
|
||||
if errored
|
||||
return
|
||||
|
||||
#form = event.target
|
||||
@setState({updating: true})
|
||||
window.recurly.token(@elements, form, @onRecurlyToken)
|
||||
|
||||
onUpdateFail: (jqXHR) ->
|
||||
handled = false
|
||||
|
||||
|
|
@ -643,8 +902,10 @@ profileUtils = context.JK.ProfileUtils
|
|||
errors = JSON.parse(jqXHR.responseText)
|
||||
handled = true
|
||||
@setState({updateErrors: errors})
|
||||
else
|
||||
console.log("error path not taken", jqXHR)
|
||||
|
||||
onSubmit: (e) ->
|
||||
onSubmitOld: (e) ->
|
||||
@resetErrors()
|
||||
|
||||
e.preventDefault()
|
||||
|
|
@ -761,8 +1022,8 @@ profileUtils = context.JK.ProfileUtils
|
|||
#if @state.shouldShowName
|
||||
window.UserActions.refresh()
|
||||
|
||||
if response.uncollectables
|
||||
context.JK.Banner.showAlert('Credit Card Updated', 'Than you. Your credit card info has been updated.<br/><br/>We will try to bill any unpaid lessons within the next hour, and an email will be sent at that time.')
|
||||
if response.past_due
|
||||
context.JK.Banner.showAlert('Credit Card Updated', 'Thank you. Your credit card info has been updated.<br/><br/>We will try to bill any unpaid lessons within the next hour, and an email will be sent at that time.')
|
||||
else
|
||||
@app.layout.notify({title: 'Credit Card Updated', text: 'Your credit card info has been updated.'})
|
||||
|
||||
|
|
|
|||
|
|
@ -49,17 +49,12 @@ profileUtils = context.JK.ProfileUtils
|
|||
|
||||
if @state.subscription
|
||||
|
||||
if @state.subscription.plan
|
||||
currentSubscription = `<CurrentSubscription subscription={this.state.subscription} />`
|
||||
currentSubscription = `<CurrentSubscription subscription={this.state.subscription} app={this.app}/>`
|
||||
|
||||
createSubscription = `<Subscription subscription={this.state.subscription}/>`
|
||||
content = `<div>
|
||||
<div className="current-subscription-block">
|
||||
{currentSubscription}
|
||||
</div>
|
||||
<div className="payment-block">
|
||||
{createSubscription}
|
||||
</div>
|
||||
</div>`
|
||||
else
|
||||
content = `<div className="loading">Loading...</div>`
|
||||
|
|
@ -72,9 +67,6 @@ profileUtils = context.JK.ProfileUtils
|
|||
<div className="profile-wrapper">
|
||||
<div className="main-content">
|
||||
{content}
|
||||
<div className="actions">
|
||||
<a onClick={this.onCancel}>LEAVE</a>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,90 +10,202 @@ AppStore = context.AppStore
|
|||
|
||||
mixins: [Reflux.listenTo(AppStore, "onAppInit")]
|
||||
|
||||
|
||||
getInitialState: () ->
|
||||
{
|
||||
selectedPlan: null
|
||||
updating: false
|
||||
}
|
||||
|
||||
getDisplayNameTier: (plan_code) ->
|
||||
if plan_code == ''
|
||||
plan_code = null
|
||||
for subscriptionCode in gon.global.subscription_codes
|
||||
if plan_code == subscriptionCode.id
|
||||
return subscriptionCode.name
|
||||
return "Unknown plan code=#{plan_code}"
|
||||
|
||||
getDisplayNamePrice: (plan_code) ->
|
||||
if plan_code == ''
|
||||
plan_code = null
|
||||
for subscriptionCode in gon.global.subscription_codes
|
||||
if plan_code == subscriptionCode.id
|
||||
return subscriptionCode.price
|
||||
return "Unknown plan code=#{plan_code}"
|
||||
|
||||
onPlanChanged: (e) ->
|
||||
val = $(e.target).val()
|
||||
val = e.target.value
|
||||
@setState({selectedPlan: val})
|
||||
|
||||
currentPlan: () ->
|
||||
this.state.selectedPlan || this.props.selectedPlan || ''
|
||||
if this.state.selectedPlan?
|
||||
this.state.selectedPlan
|
||||
else
|
||||
this.props.subscription.desired_plan_code || ''
|
||||
|
||||
onChangeSubmit: (event) ->
|
||||
form = event.target
|
||||
event.preventDefault()
|
||||
onSubmit: (event) ->
|
||||
if event
|
||||
event.preventDefault()
|
||||
@performSubmit()
|
||||
|
||||
if !@state.selectedPlan
|
||||
onChangeSubmit: (form) ->
|
||||
if @state.updating
|
||||
return
|
||||
SubscriptionActions.changeSubscription(this.state.selectedPlan)
|
||||
@performSubmit()
|
||||
|
||||
performSubmit: () ->
|
||||
if !@state.selectedPlan?
|
||||
return
|
||||
|
||||
@setState({updating: true})
|
||||
#SubscriptionActions.changeSubscription(this.state.selectedPlan)
|
||||
rest.changeSubscription(this.state.selectedPlan).done((subscription) =>
|
||||
SubscriptionActions.forceUpdate(subscription)
|
||||
has_billing_info = subscription.has_billing_info
|
||||
console.log("subscription change update", subscription)
|
||||
if has_billing_info
|
||||
@props.app.layout.notify({
|
||||
title: "Subscription updated!",
|
||||
text: "Thank you for supporting JamKazam!"
|
||||
})
|
||||
else
|
||||
@props.app.layout.notify({
|
||||
title: "Payment method still needed",
|
||||
text: "Please click UPDATE PAYMENT METHOD in the bottom-right of the screen."
|
||||
})
|
||||
@setState({updating: false, selectedPlan: null})
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
@setState({updating: false})
|
||||
if jqXHR.status == 422
|
||||
@props.app.layout.notify({
|
||||
title: "you already have this subscription",
|
||||
text: "No changes were made to your account."
|
||||
})
|
||||
else
|
||||
@props.app.layout.notify({
|
||||
title: "unable to update subscription status",
|
||||
text: "Please contact support@jamkazam.com. Error:\n #{jqXHR.responseText}"
|
||||
})
|
||||
)
|
||||
|
||||
onCancelPlan: (event) ->
|
||||
if confirm("Are you sure you want to cancel your plan?")
|
||||
SubscriptionActions.cancelSubscription()
|
||||
|
||||
componentDidUpdate: () ->
|
||||
if @state.updating
|
||||
@setState({updating:false})
|
||||
|
||||
componentDidMount: () ->
|
||||
|
||||
@root = $(@getDOMNode())
|
||||
|
||||
document.querySelector('#change-subscription-form').addEventListener('submit', @onChangeSubmit.bind(this))
|
||||
|
||||
comparePlans: (e) ->
|
||||
if context.JK.clientType() == 'client'
|
||||
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
|
||||
e.preventDefault()
|
||||
|
||||
render: () ->
|
||||
plan_codes = []
|
||||
monthlies = []
|
||||
yearlies = []
|
||||
for plan in gon.global.subscription_codes
|
||||
plan_codes.push(`<option key={plan.id} value={plan.id}>{plan.name} ({plan.price}/month)</option>`)
|
||||
if plan.cycle == 'month'
|
||||
monthlies.push(plan)
|
||||
else
|
||||
yearlies.push(plan)
|
||||
plan_codes.push(`<option key="monthly" value="monthly" disabled="disabled">-------- MONTHLY PLANS --------</option>`)
|
||||
for plan in monthlies
|
||||
plan_codes.push(`<option key={plan.id || ''} value={plan.id || ''}>{plan.name} (${plan.price.toFixed(2)}/{plan.cycle})</option>`)
|
||||
plan_codes.push(`<option key="yearly" value="yearly" disabled="disabled">-------- YEARLY PLANS --------</option>`)
|
||||
for plan in yearlies
|
||||
plan_codes.push(`<option key={plan.id || ''} value={plan.id || ''}>{plan.name} (${plan.price.toFixed(2)}/{plan.cycle})</option>`)
|
||||
plansJsx = `<select name="plan_code" onChange={this.onPlanChanged} defaultValue={this.currentPlan()} >{plan_codes}</select>`
|
||||
|
||||
plansJsx = `
|
||||
<select name="plan_code" onChange={this.onPlanChanged} value={this.currentPlan()} >{plan_codes}</select>`
|
||||
|
||||
changeClass = 'button-orange'
|
||||
if !@state.selectedPlan
|
||||
changeClass = 'button-orange update-plan'
|
||||
if !@state.selectedPlan? || @state.updating
|
||||
changeClass = changeClass + ' disabled'
|
||||
|
||||
|
||||
if @props.subscription.pending_subscription
|
||||
currentPlan = this.getDisplayNameTier(this.props.subscription.pending_subscription.plan.plan_code)
|
||||
billingAddendum = `<span>(billed at {this.getDisplayNameTier(this.props.subscription.plan.plan_code)} until next billing cycle</span>`
|
||||
recurly_subscription = @props.subscription.subscription
|
||||
effective_plan_name = `<span className="plan-name">{this.getDisplayNameTier(this.props.subscription.plan_code)}</span>`
|
||||
desired_plan_name = `<span className="plan-name">{this.getDisplayNameTier(this.props.subscription.desired_plan_code)}</span>`
|
||||
admin_override_plan_name = `<span className="plan-name">{this.getDisplayNameTier(this.props.subscription.admin_override_plan_code)}</span>`
|
||||
in_trial = @props.subscription.in_trial
|
||||
effective_is_free = !!this.props.subscription.plan_code
|
||||
has_pending_subscription = @props.subscription.subscription?.pending_subscription?
|
||||
cancelled_subscription = @props.subscription.subscription?.remaining_billing_cycles == 0
|
||||
show_payment_info = null
|
||||
has_billing_info = @props.subscription.has_billing_info
|
||||
|
||||
#console.log("@props.subscription.subscription", @props.subscription.subscription, has_pending_subscription)
|
||||
|
||||
if in_trial
|
||||
if @props.subscription.desired_plan_code
|
||||
if has_billing_info
|
||||
note = `<span>Billing starts for the {desired_plan_name} plan after the trial ends.</span>`
|
||||
else
|
||||
warning = `<p className="uncollectable-msg">You will drop to the <span className="plan-name">Free</span> free plan after the trial ends because you have not yet entered payment info.</p>`
|
||||
show_payment_info = true
|
||||
else
|
||||
if has_billing_info
|
||||
warning = `<p className="uncollectable-msg">You will drop to the <span className="plan-name">Free</span> free plan after the trial ends because you have not selected a plan.</p>`
|
||||
else
|
||||
warning = `<p className="uncollectable-msg">You will drop to the <span className="plan-name">Free</span> free plan after the trial ends because you have not yet entered payment info or selected a plan.</p>`
|
||||
show_payment_info = true
|
||||
|
||||
explanation = `<span>You have a <span className="plan-name">Gold</span> account until your trial ends {context.JK.formatDateShort(this.props.subscription.trial_ends_at)}. {note}</span>`
|
||||
else
|
||||
if @props.subscription.desired_plan_code && !@props.subscription.plan_code && !has_billing_info
|
||||
explanation = `<span>You have successfully upgraded your plan to the {desired_plan_name} level, thank you!</span>`
|
||||
warning = `<p className="uncollectable-msg">For this plan to take effect, you must provide a payment method (e.g. a credit card), for the monthly subscription charge. Please click the Update Payment Method button to do this now.</p>`
|
||||
show_payment_info = true
|
||||
else
|
||||
if @props.subscription.desired_plan_code
|
||||
if !has_billing_info
|
||||
show_payment_info = true
|
||||
explanation = `<span>You have successfully upgraded your plan to the {desired_plan_name} level, thank you</span>`
|
||||
warning = `<p className="uncollectable-msg">However, you must provide a payment method (e.g. a credit card), for the monthly subscription charge. Please click the Update Payment Method button to do this now.</p>`
|
||||
else
|
||||
explanation = `<span>You have successfully upgraded your plan to the {desired_plan_name} level, thank you!</span>`
|
||||
|
||||
else
|
||||
# free plan situation - not much to go on about
|
||||
explanation = `<span>You are currently on the {desired_plan_name} plan.</span>`
|
||||
if show_payment_info
|
||||
update_payment_btn = `<a className="button-orange update-payment-method" href="/client#/account/paymentHistory">UPDATE PAYMENT METHOD</a>`
|
||||
if has_pending_subscription
|
||||
billingAddendum = null #`<span><br/><br/><span>You will be billed next at the <span className="plan-name">{this.getDisplayNameTier(this.props.subscription.subscription.plan.plan_code)}</span> on the next billing cycle.</span></span>`
|
||||
else if cancelled_subscription && this.props.subscription.desired_plan_code == null && this.props.subscription.plan_code != null
|
||||
billingAddendum = `<span>However, your cancelled {effective_plan_name} plan is still active until the end of the billing cycle.</span>`# `<span><br/><br/><span>You will be billed a final time at the <span className="plan-name">{this.getDisplayNameTier(this.props.subscription.subscription.plan.plan_code)}</span> at end of this billing cycle.</span></span>`
|
||||
else
|
||||
currentPlan = this.getDisplayNameTier(this.props.subscription.plan.plan_code)
|
||||
billingAddendum = null
|
||||
`<div>
|
||||
`<div className="current-subscription">
|
||||
<div>
|
||||
<h3>Current Subscription</h3>
|
||||
<div>
|
||||
{currentPlan}
|
||||
{billingAddendum}
|
||||
<h2>Subscription:</h2>
|
||||
<p className="explainer">Your JamKazam subscription is currently set to the plan displayed below. To compare the features available for different subscription plans, click the Compare Plans button below. To change your plan, click the "subscription plan" list box below, select a new plan, and then click the Update Plan button below.</p>
|
||||
<div className="subscription-plan-and-status">
|
||||
<div className="subscription-plan">
|
||||
<form id="change-subscription-form" onSubmit={this.onChangeSubmit} >
|
||||
<label for="plan_code">subscription plan:</label>
|
||||
<div className="subscription-actions">
|
||||
{plansJsx}
|
||||
<a className="button-grey" onClick={this.comparePlans} target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-">COMPARE PLANS</a>
|
||||
<button className={changeClass} onClick={this.onSubmit} type="submit">UPDATE PLAN</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="effective-subscription">
|
||||
<div className="explanation">
|
||||
<p>{explanation} {billingAddendum}</p>
|
||||
{warning}
|
||||
</div>
|
||||
<a className="button-grey" onClick={this.onCancel}>BACK</a>{update_payment_btn}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Change Plan</h3>
|
||||
<form id="change-subscription-form">
|
||||
|
||||
<label for="plan_code">Change Plan To:</label>
|
||||
{plansJsx}
|
||||
|
||||
<button className={changeClass}>CHANGE PLAN</button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Cancel Plan</h3>
|
||||
<button className="button-orange" onClick={this.onCancelPlan}>CANCEL PLAN</button>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
})
|
||||
|
|
@ -15,7 +15,6 @@ logger = context.JK.logger
|
|||
{selectedCountry: null, countries: LocationStore.countries || {US: {name: 'United States', regions: []}}}
|
||||
|
||||
onLocationsChanged: (countries) ->
|
||||
console.log("countires in ", countries)
|
||||
@setState({countries: countries})
|
||||
|
||||
onCountryChanged: (e) ->
|
||||
|
|
|
|||
|
|
@ -12,9 +12,31 @@ RecordingActions = @RecordingActions
|
|||
getInitialState: () ->
|
||||
{childWindow: null, isRecording: false}
|
||||
|
||||
openBrowserToPayment: () ->
|
||||
context.JK.popExternalLink("/client#/account/subscription", true)
|
||||
|
||||
openBrowserToPlanComparison: () ->
|
||||
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
|
||||
return 'noclose'
|
||||
|
||||
openRecording: () ->
|
||||
|
||||
RecordingActions.openRecordingControls()
|
||||
canRecord = window.SessionStore.canRecord()
|
||||
if canRecord
|
||||
openRecordingControls()
|
||||
else
|
||||
buttons = []
|
||||
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
|
||||
buttons.push({name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (@openBrowserToPlanComparison()))})
|
||||
buttons.push({
|
||||
name: 'UPGRADE PLAN',
|
||||
buttonStyle: 'button-orange',
|
||||
click: (() => (@openBrowserToPayment()))
|
||||
})
|
||||
context.JK.Banner.show({
|
||||
title: "Your Current Plan Does Not Allow Recording",
|
||||
html: context._.template($('#template-plan-no-record').html(), {}, { variable: 'data' }),
|
||||
buttons: buttons})
|
||||
|
||||
render: () ->
|
||||
`<a className="session-record button-grey left" data-is-recording={this.state.isRecording} onClick={this.openRecording}>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,33 @@ SessionActions = @SessionActions
|
|||
|
||||
@SessionVideoBtn = React.createClass({
|
||||
|
||||
openBrowserToPayment: () ->
|
||||
context.JK.popExternalLink("/client#/account/subscription", true)
|
||||
|
||||
openBrowserToPlanComparison: () ->
|
||||
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
|
||||
return 'noclose'
|
||||
|
||||
sessionWebCam: (e) ->
|
||||
e.preventDefault();
|
||||
|
||||
SessionActions.toggleSessionVideo()
|
||||
canVideo = window.SessionStore.canRecord()
|
||||
|
||||
if canVideo
|
||||
SessionActions.toggleSessionVideo()
|
||||
else
|
||||
buttons = []
|
||||
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
|
||||
buttons.push({name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (@openBrowserToPlanComparison()))})
|
||||
buttons.push({
|
||||
name: 'UPGRADE PLAN',
|
||||
buttonStyle: 'button-orange',
|
||||
click: (() => (@openBrowserToPayment()))
|
||||
})
|
||||
context.JK.Banner.show({
|
||||
title: "Your Current Plan Does Not Allow Video",
|
||||
html: context._.template($('#template-plan-no-video').html(), {}, { variable: 'data' }),
|
||||
buttons: buttons})
|
||||
|
||||
render: () ->
|
||||
`<a className="session-share button-grey-toggle left" onClick={this.sessionWebCam}>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ UserStore = context.UserStore
|
|||
|
||||
configureRecurly: () =>
|
||||
unless window.configuredRecurly
|
||||
context.recurly.configure(gon.global.recurly_public_api_key)
|
||||
window.recurly.configure(gon.global.recurly_public_api_key)
|
||||
window.configuredRecurly = true
|
||||
|
||||
|
||||
|
|
@ -87,6 +87,7 @@ UserStore = context.UserStore
|
|||
}
|
||||
}
|
||||
)
|
||||
console.log("attaching", $('#subscription-elements'))
|
||||
cardElement.attach('#subscription-elements')
|
||||
|
||||
@root = $(@getDOMNode())
|
||||
|
|
@ -132,7 +133,7 @@ UserStore = context.UserStore
|
|||
<input type="text" data-recurly="address1" required autocomplete="shipping address-line1"></input>
|
||||
|
||||
<label for="address2">Address 2:</label>
|
||||
<input type="text" data-recurly="address2" required autocomplete="shipping address-line1"></input>
|
||||
<input type="text" data-recurly="address2" required autocomplete="shipping address-line2"></input>
|
||||
|
||||
<label for="city">City:</label>
|
||||
<input type="text" data-recurly="city" required autocomplete="shipping address-level2"></input>
|
||||
|
|
@ -145,10 +146,11 @@ UserStore = context.UserStore
|
|||
|
||||
<label for="country">Country:</label>
|
||||
{countryJsx}
|
||||
|
||||
<label for="plan_code">Plan:</label>
|
||||
{plansJsx}
|
||||
</div>
|
||||
<div id="subscription-elements">
|
||||
<div id="subscription-elements-legacy">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="recurly-token" data-recurly="token"></input>
|
||||
|
|
|
|||
|
|
@ -44,33 +44,33 @@ context = window
|
|||
|
||||
openBrowserToPayment: (e) ->
|
||||
context.JK.popExternalLink("/client#/account/subscription", true)
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
openBrowserToPlanComparison: (e) ->
|
||||
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return 'noclose'
|
||||
|
||||
|
||||
render: () ->
|
||||
content = null
|
||||
if @props.subscription.until.total < 2000
|
||||
if @props.subscription.until.total < 60000
|
||||
content = `<div className="message">
|
||||
<p>You have run out of time.</p>
|
||||
<p>You can <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrade your plan</a> to continue playing.</p>
|
||||
<p><a href="#" onClick={this.openBrowserToPlanComparison}>Compare plans</a> and consider <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrading your plan</a> for more play time and premium features.</p>
|
||||
</div>`
|
||||
else
|
||||
if @props.subscription.main_concern_type == 'remaining_session_play_time'
|
||||
content = `<div className="message">
|
||||
<p>You will run out play time for this session in:</p>
|
||||
<p className="time">{this.displayTime()}</p>
|
||||
<p>You can <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrade your plan</a> to continue playing.</p>
|
||||
<p><a href="#" onClick={this.openBrowserToPlanComparison}>Compare plans</a> and consider <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrading your plan</a> for more play time and premium features.</p>
|
||||
</div>`
|
||||
else
|
||||
content = `<div className="message">
|
||||
<p>You will run out of monthly play time in:</p>
|
||||
<p className="time">{this.displayTime()}</p>
|
||||
<p>You can <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrade your plan</a> to continue playing.</p>
|
||||
<p><a href="#" onClick={this.openBrowserToPlanComparison}>Compare plans</a> and consider <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrading your plan</a> for more play time and premium features.</p>
|
||||
</div>`
|
||||
|
||||
if content?
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ context = window
|
|||
changeSubscription: {}
|
||||
cancelSubscription: {}
|
||||
updatePayment: {}
|
||||
forceUpdate: {}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ BroadcastStore = Reflux.createStore(
|
|||
|
||||
subscriptionManagement: () ->
|
||||
@subscriptionConcern.until = @lessonUtils.getTimeRemaining(@subscriptionConcern.main_concern_time)
|
||||
if @subscriptionConcern.until.total < -15000
|
||||
if @subscriptionConcern.until.total < -30000
|
||||
leaveBehavior =
|
||||
location: "/client#/findSession"
|
||||
buttons = []
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ logger = context.JK.logger
|
|||
this.listenTo(context.AppStore, this.onAppInit)
|
||||
|
||||
changed: () ->
|
||||
console.log("changed", @countries)
|
||||
@trigger(@countries)
|
||||
|
||||
onSelectCountry: (country) ->
|
||||
|
|
|
|||
|
|
@ -1103,8 +1103,12 @@ ConfigureTracksActions = @ConfigureTracksActions
|
|||
@sessionRules.remaining_session_until = until_time
|
||||
|
||||
|
||||
if sessionData.subscription_rules
|
||||
@subscriptionRules = sessionData.subscription_rules
|
||||
if sessionData.subscription
|
||||
# for the backend - it looks here
|
||||
#sessionData.subscription = sessionData.subscription_rules
|
||||
# let the backend know
|
||||
#context.jamClient.applySubscriptionPolicy()
|
||||
@subscriptionRules = sessionData.subscription
|
||||
# TESTING:
|
||||
#@subscriptionRules.remaining_month_play_time = 60 * 30 + 15 # 30 minutes and 15 seconds
|
||||
|
||||
|
|
@ -1114,9 +1118,7 @@ ConfigureTracksActions = @ConfigureTracksActions
|
|||
#until_time.setSeconds(until_time.getSeconds() + @subscriptionRules.remaining_month_play_time)
|
||||
@subscriptionRules.remaining_month_until = until_time
|
||||
|
||||
|
||||
@currentSession = sessionData
|
||||
|
||||
if context.jamClient.UpdateSessionInfo?
|
||||
if @currentSession?
|
||||
context.jamClient.UpdateSessionInfo(@currentSession)
|
||||
|
|
@ -1290,6 +1292,22 @@ ConfigureTracksActions = @ConfigureTracksActions
|
|||
id: () ->
|
||||
@currentSessionId
|
||||
|
||||
canRecord: () ->
|
||||
if @subscriptionRules?
|
||||
console.log("can record? rules:", @subscriptionRules.can_record_audio )
|
||||
return @subscriptionRules.can_record_audio
|
||||
else
|
||||
console.log("can record? no rules; allow")
|
||||
return true
|
||||
|
||||
canVideo: () ->
|
||||
if @subscriptionRules?
|
||||
console.log("can video? rules:", @subscriptionRules.can_use_video)
|
||||
return @subscriptionRules.can_use_video
|
||||
else
|
||||
conssole.log("can video? no rules; allow")
|
||||
return true
|
||||
|
||||
getCurrentOrLastSession: () ->
|
||||
@currentOrLastSession
|
||||
|
||||
|
|
|
|||
|
|
@ -16,39 +16,21 @@ logger = context.JK.logger
|
|||
|
||||
onUpdateSubscription: (subscription) ->
|
||||
|
||||
console.log("refreshing subscription info")
|
||||
rest.getSubscription().done (subscription) =>
|
||||
@subscription = subscription
|
||||
console.log("subscription store update", subscription)
|
||||
@trigger(@subscription)
|
||||
.fail(() =>
|
||||
@app.layout.notify({
|
||||
title: "unable to fetch subscription status",
|
||||
title: "Unable to fetch subscription status",
|
||||
text: "Please contact support@jamkazam.com"
|
||||
})
|
||||
)
|
||||
|
||||
onChangeSubscription: (plan_code) ->
|
||||
rest.changeSubscription({plan_code: plan_code}).done((subscription) =>
|
||||
@subscription = subscription
|
||||
console.log("subscription change update", subscription)
|
||||
@app.layout.notify({
|
||||
title: "Subscription updated!",
|
||||
text: "Thank you for supporting JamKazam!"
|
||||
})
|
||||
@trigger(@subscription)
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
if jqXHR.status == 422
|
||||
@app.layout.notify({
|
||||
title: "you already have this subscription",
|
||||
text: "No changes were made to your account."
|
||||
})
|
||||
else
|
||||
@app.layout.notify({
|
||||
title: "unable to update subscription status",
|
||||
text: "Please contact support@jamkazam.com. Error:\n #{jqXHR.responseText}"
|
||||
})
|
||||
)
|
||||
onForceUpdate: (subscription) ->
|
||||
@subscription = subscription
|
||||
@trigger(@subscription)
|
||||
|
||||
onCancelSubscription: () ->
|
||||
rest.cancelSubscription().done((result) =>
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@ logger = context.JK.logger
|
|||
reactContext = if window.opener? then window.opener else window
|
||||
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
|
||||
if window.opener?
|
||||
console.log("WINDOW.opener", window.opener)
|
||||
try
|
||||
m = window.opener.MixerStore
|
||||
if !m?
|
||||
console.log("it's still not set")
|
||||
reactContext = window
|
||||
catch e
|
||||
reactContext = window
|
||||
|
||||
|
||||
userAgent = window.navigator.userAgent;
|
||||
if /iPhone|iPad|iPod|android/i.test(navigator.userAgent)
|
||||
# iPad or iPhone
|
||||
|
|
|
|||
|
|
@ -1,5 +1,48 @@
|
|||
@import 'common.scss';
|
||||
|
||||
|
||||
.recurly-element{
|
||||
background-color:#c5c5c5 !important;
|
||||
position: relative;
|
||||
#width: 100%;
|
||||
height: 25px;
|
||||
#border: 2px solid black;
|
||||
//background: transparent;
|
||||
margin: 0 0 10px;
|
||||
outline: none;
|
||||
#font-family: 'Open Sans', Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
color: black;
|
||||
-webkit-appearance: none;
|
||||
-webkit-transition: border-color 0.3s;
|
||||
-moz-transition: border-color 0.3s;
|
||||
-ms-transition: border-color 0.3s;
|
||||
-o-transition: border-color 0.3s;
|
||||
transition: border-color 0.3s;
|
||||
display:inline-block;
|
||||
width: calc(100% - 150px);
|
||||
@include border_box_sizing;
|
||||
max-width:400px;
|
||||
padding-left:6px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.recurly-element-month {
|
||||
width:40px;
|
||||
text-align:center;
|
||||
}
|
||||
.recurly-element-year {
|
||||
width:60px;
|
||||
text-align:center;
|
||||
}
|
||||
.recurly-element-cvv {
|
||||
width:60px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
#account-payment-history {
|
||||
|
||||
.content-body-scroller {
|
||||
|
|
@ -139,22 +182,54 @@
|
|||
}
|
||||
}
|
||||
|
||||
input {
|
||||
|
||||
.recurly-element {
|
||||
height:25px;
|
||||
}
|
||||
|
||||
#subscription-elements-month, #subscription-elements-cvv {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
//.recurly-element {
|
||||
// background-color:white;
|
||||
//}
|
||||
|
||||
input, select {
|
||||
display:inline-block;
|
||||
width: calc(100% - 150px);
|
||||
@include border_box_sizing;
|
||||
max-width:200px;
|
||||
font-family: Helvetica, serif;
|
||||
font-weight:normal;
|
||||
font-size:16px;
|
||||
border-radius:0;
|
||||
}
|
||||
.field {
|
||||
position:relative;
|
||||
display:block;
|
||||
margin-top:15px;
|
||||
margin-bottom:25px;
|
||||
margin-bottom:10px;
|
||||
|
||||
label {
|
||||
width:150px;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
border: solid 0px #900;
|
||||
|
||||
label {
|
||||
color:red;
|
||||
font-weight:bold;
|
||||
}
|
||||
input, select {
|
||||
background-color: #c5c5c5 !important;
|
||||
margin-bottom: 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.uncollectable-msg {
|
||||
background-color:black;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
|
||||
#account-subscription {
|
||||
.button-orange {margin:0;}
|
||||
div[data-react-class="AccountSubscriptionScreen"] {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -29,12 +30,19 @@
|
|||
|
||||
}
|
||||
|
||||
.uncollectable-msg {
|
||||
background-color:black;
|
||||
color:white;
|
||||
padding:20px;
|
||||
margin:20px 0;
|
||||
}
|
||||
|
||||
#change-subscription-form {
|
||||
max-width:35rem;
|
||||
display:grid;
|
||||
align-items: center;
|
||||
#display:grid;
|
||||
#align-items: center;
|
||||
#justify-content: center;
|
||||
grid-template-columns: 8rem 8rem 8rem;
|
||||
#grid-template-columns: 8rem 8rem 8rem;
|
||||
}
|
||||
|
||||
.payment-block {
|
||||
|
|
@ -77,7 +85,6 @@
|
|||
outline: none;
|
||||
font-family: 'Open Sans', Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
box-shadow: none;
|
||||
#border-radius: 0;
|
||||
color: black;
|
||||
|
|
@ -148,6 +155,7 @@
|
|||
}
|
||||
|
||||
.item > label {
|
||||
font-weight:normal;
|
||||
display: inline;
|
||||
text-transform: none;
|
||||
}
|
||||
|
|
@ -161,4 +169,59 @@
|
|||
margin:30px 0 20px;
|
||||
}
|
||||
|
||||
.current-subscription {
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
margin-bottom:15px;
|
||||
color:white;
|
||||
width:35rem;
|
||||
}
|
||||
p.explainer{
|
||||
width:35rem;
|
||||
}
|
||||
label {
|
||||
text-transform: none;
|
||||
font-size:1rem;
|
||||
color:white;
|
||||
}
|
||||
button,a {
|
||||
margin:0;
|
||||
}
|
||||
.plan-name {
|
||||
font-weight:bold;
|
||||
}
|
||||
.subscription-plan-and-status {
|
||||
margin-top:30px;
|
||||
}
|
||||
.subscription-actions {
|
||||
margin-top:10px;
|
||||
}
|
||||
.subscription-plan {
|
||||
display:inline-block;
|
||||
width:50%;
|
||||
padding-right:20px;
|
||||
}
|
||||
.subscription-status {
|
||||
display:inline-block;
|
||||
width:50%;
|
||||
}
|
||||
p {
|
||||
margin:0;
|
||||
}
|
||||
.update-plan {
|
||||
float:right;
|
||||
}
|
||||
.effective-subscription {
|
||||
margin-top:30px;
|
||||
margin-bottom:60px;
|
||||
width:35rem;
|
||||
}
|
||||
.explanation {
|
||||
margin-bottom:20px;
|
||||
}
|
||||
.update-payment-method {
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -342,11 +342,24 @@ body.web {
|
|||
|
||||
}
|
||||
|
||||
.create-account-header {
|
||||
.create-account-header, .subscription-type-header {
|
||||
padding-top:10px;
|
||||
margin-left:10px;
|
||||
}
|
||||
|
||||
.radio-field {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
.subscription-options {
|
||||
|
||||
margin-top:16px;
|
||||
margin-left:49px;
|
||||
|
||||
label {
|
||||
display:inline;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top:20px;
|
||||
input, a {
|
||||
|
|
@ -401,8 +414,63 @@ body.web {
|
|||
|
||||
}
|
||||
|
||||
.left-side {
|
||||
float:left;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
width:30rem;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
margin-left:25px;
|
||||
margin-left:40px;
|
||||
float:left;
|
||||
width:25rem;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width:320px){
|
||||
.left-side {
|
||||
float:none;
|
||||
clear:both;
|
||||
}
|
||||
.right-side {
|
||||
margin-top:20px;
|
||||
margin-left:0;
|
||||
float:none;
|
||||
clear:both;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:900px){
|
||||
.left-side {
|
||||
float:left;
|
||||
clear:none;
|
||||
}
|
||||
.right-side {
|
||||
margin-top:0;
|
||||
margin-left:40px;
|
||||
float:left;
|
||||
width:25rem;
|
||||
clear:none;
|
||||
}
|
||||
}
|
||||
|
||||
.comparison-block {
|
||||
margin:15px 0 40px 25px;
|
||||
a {
|
||||
margin-bottom:15px;
|
||||
}
|
||||
|
||||
}
|
||||
.pricing-info {
|
||||
font-style:italic;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -90,9 +90,20 @@ class ApiRecurlyController < ApiController
|
|||
|
||||
# get Recurly payment history
|
||||
def payment_history
|
||||
@payments=@client.payment_history(current_user)
|
||||
@payments=@client.payment_history(current_user, params)
|
||||
render :json => {payments: @payments}
|
||||
rescue RecurlyClientError => x
|
||||
puts "Recurly error #{current_user.email}, #{x}"
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
def invoice_history
|
||||
@invoices, account= @client.invoice_history(current_user, params)
|
||||
past_due = account && account.state == 'past_due'
|
||||
|
||||
render :json => {entries: @invoices, past_due: past_due}
|
||||
rescue RecurlyClientError => x
|
||||
puts "Recurly error #{current_user.email}, #{x}"
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
|
|
@ -124,6 +135,30 @@ class ApiRecurlyController < ApiController
|
|||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
def update_payment
|
||||
begin
|
||||
recurly_token = params[:recurly_token]
|
||||
account = @client.find_or_create_account(current_user, nil, recurly_token)
|
||||
@client.update_billing_info_from_token(current_user, account, recurly_token)
|
||||
current_user.update_attribute(:stored_credit_card, true)
|
||||
past_due = account && account.has_past_due_invoice
|
||||
subscription = nil
|
||||
|
||||
plan_code = current_user.desired_plan_code
|
||||
result, subscription, account = @client.handle_create_subscription(current_user, plan_code, account)
|
||||
has_billing_info = !account.nil? && !account[:billing_info].nil?
|
||||
render :json => {
|
||||
past_due: past_due,
|
||||
subscription: subscription, has_billing_info: has_billing_info,
|
||||
plan_code: current_user.subscription_plan_code,
|
||||
desired_plan_code: current_user.desired_plan_code,
|
||||
in_trial: !current_user.subscription_trial_ended?,
|
||||
trial_ends_at: current_user.subscription_trial_ends_at
|
||||
}, :status => 200
|
||||
rescue RecurlyClientError => x
|
||||
render json: {:message => x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
end
|
||||
def create_subscription
|
||||
begin
|
||||
sale = Sale.purchase_subscription(current_user, params[:recurly_token], params[:plan_code])
|
||||
|
|
@ -135,15 +170,21 @@ class ApiRecurlyController < ApiController
|
|||
end
|
||||
|
||||
def get_subscription
|
||||
subscription = @client.find_subscription(current_user)
|
||||
subscription, account = @client.find_subscription(current_user)
|
||||
past_due = account && account.has_past_due_invoice
|
||||
has_billing_info = !account.nil? && !account[:billing_info].nil?
|
||||
|
||||
if subscription
|
||||
render :json => subscription.to_json
|
||||
else
|
||||
render :json => {}
|
||||
end
|
||||
render :json => {
|
||||
past_due: past_due,
|
||||
subscription: subscription, has_billing_info: has_billing_info,
|
||||
plan_code: current_user.subscription_plan_code,
|
||||
desired_plan_code: current_user.desired_plan_code,
|
||||
in_trial: !current_user.subscription_trial_ended?,
|
||||
trial_ends_at: current_user.subscription_trial_ends_at
|
||||
}, :status => 200
|
||||
end
|
||||
|
||||
|
||||
def cancel_subscription
|
||||
begin
|
||||
@client.cancel_subscription(current_user.recurly_subscription_id)
|
||||
|
|
@ -161,19 +202,45 @@ class ApiRecurlyController < ApiController
|
|||
|
||||
def change_subscription_plan
|
||||
begin
|
||||
result = @client.change_subscription_plan(current_user, params[:plan_code])
|
||||
plan_code = params[:plan_code]
|
||||
if plan_code == ''
|
||||
plan_code = nil
|
||||
end
|
||||
|
||||
result, subscription, account = @client.update_desired_subscription(current_user, plan_code)
|
||||
has_billing_info = !account.nil? && !account[:billing_info].nil?
|
||||
past_due = account && account.has_past_due_invoice
|
||||
if !result
|
||||
render json: {:message => "No change made to plan"}, :status => 422
|
||||
else
|
||||
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
|
||||
render :json => subscription.to_json
|
||||
render :json => {
|
||||
past_due: past_due,
|
||||
subscription:subscription,
|
||||
has_billing_info: has_billing_info,
|
||||
plan_code: current_user.subscription_plan_code,
|
||||
desired_plan_code: current_user.desired_plan_code,
|
||||
in_trial: !current_user.subscription_trial_ended?,
|
||||
trial_ends_at: current_user.subscription_trial_ends_at
|
||||
}
|
||||
end
|
||||
|
||||
rescue RecurlyClientError => x
|
||||
render json: {:message => x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
def list_invoices
|
||||
begin
|
||||
account = @client.get_account(current_user)
|
||||
|
||||
if account.nil?
|
||||
render json:[], :status => 200
|
||||
else
|
||||
return @client.list_invoices(current_user)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def change_subscription_payment
|
||||
begin
|
||||
@client.change_subscription_payment(current_user.recurly_subscription_id, params[:recurly_token], params[:billing_info])
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class UsersController < ApplicationController
|
|||
|
||||
@invited_user = load_invited_user(params)
|
||||
|
||||
@plan_code = params[:plan_code] || ''
|
||||
|
||||
if @invited_user && @invited_user.email
|
||||
user = User.find_by_email(@invited_user.email)
|
||||
if user && (!current_user || current_user.id != user.id)
|
||||
|
|
@ -162,6 +164,11 @@ class UsersController < ApplicationController
|
|||
|
||||
birth_date = fixup_birthday(params[:jam_ruby_user]["birth_date(2i)"], params[:jam_ruby_user]["birth_date(3i)"], params[:jam_ruby_user]["birth_date(1i)"])
|
||||
location = { :country => params[:jam_ruby_user][:country], :state => params[:jam_ruby_user][:state], :city => params[:jam_ruby_user][:city]}
|
||||
desired_plan_code = params[:jam_ruby_user][:desired_plan_code]
|
||||
if desired_plan_code ==''
|
||||
desired_plan_code = nil
|
||||
end
|
||||
|
||||
terms_of_service = params[:jam_ruby_user][:terms_of_service].nil? || params[:jam_ruby_user][:terms_of_service] == "0"? false : true
|
||||
musician = params[:jam_ruby_user][:musician]
|
||||
|
||||
|
|
@ -183,7 +190,8 @@ class UsersController < ApplicationController
|
|||
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||
affiliate_partner: @affiliate_partner,
|
||||
timezone: current_timezone,
|
||||
origin: origin_cookie)
|
||||
origin: origin_cookie,
|
||||
desired_plan_code: desired_plan_code )
|
||||
|
||||
# check for errors
|
||||
if @user.errors.any?
|
||||
|
|
@ -191,6 +199,7 @@ class UsersController < ApplicationController
|
|||
load_location(request.remote_ip, location)
|
||||
gon.signup_errors = true
|
||||
gon.musician_instruments = instruments
|
||||
gon.plan_code = desired_plan_code
|
||||
render "new", :layout => 'web'
|
||||
else
|
||||
sign_in @user
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ else
|
|||
attributes :id, :name, :description, :musician_access, :approval_required, :friends_can_join, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id, :jam_track_id, :music_session_id_int
|
||||
|
||||
if @on_join
|
||||
node :subscription_rules do |session|
|
||||
node :subscription do |session|
|
||||
@subscription_rules
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<h2>subscription: </h2>
|
||||
</div>
|
||||
<div class="account-mid subscription">
|
||||
Manage your JamKazam subscription
|
||||
Manage your JamKazam subscription.
|
||||
</div>
|
||||
<div class="right">
|
||||
<a id="account-manage-subscription" href="#" class="button-orange">UPDATE</a>
|
||||
|
|
@ -38,15 +38,25 @@
|
|||
<hr/>
|
||||
|
||||
<div class="account-left">
|
||||
<h2>sessions: </h2>
|
||||
<h2>payments:</h2>
|
||||
</div>
|
||||
<div class="account-mid sessions">
|
||||
{{data.session}}
|
||||
|
||||
<div class="account-mid payments">
|
||||
<div class="whitespace">
|
||||
{% if (data.sales_count == 0) { %}
|
||||
Enter payment method and view payment history.
|
||||
{% } else { %}
|
||||
You have made {{data.sales_count}} purchase{{data.sales_count == 1 ? '' : 's'}}.
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<a id="account-scheduled-sessions-link" href="#" class="button-orange">VIEW</a>
|
||||
<a id="account-payment-history-link" href="#" class="button-orange">VIEW</a>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
{% if (data.isNativeClient) { %}
|
||||
|
|
@ -140,30 +150,6 @@
|
|||
</div>
|
||||
<br clear="all" />
|
||||
|
||||
{% if (data.musician) { %}
|
||||
<hr />
|
||||
|
||||
<div class="account-left">
|
||||
<h2>payments:</h2>
|
||||
</div>
|
||||
|
||||
<div class="account-mid payments">
|
||||
<div class="whitespace">
|
||||
{% if (data.sales_count == 0) { %}
|
||||
You have made no purchases.
|
||||
{% } else { %}
|
||||
You have made {{data.sales_count}} purchase{{data.sales_count == 1 ? '' : 's'}}.
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<a id="account-payment-history-link" href="#" class="button-orange">VIEW</a>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
|
||||
{% } %}
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="account-left">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
.content-icon
|
||||
= image_tag "content/icon_account.png", :size => "27x20"
|
||||
h1
|
||||
| subscription
|
||||
| my account
|
||||
= render "screen_navigation"
|
||||
.content-body
|
||||
= react_component 'AccountSubscriptionScreen', {}
|
||||
|
|
@ -280,6 +280,15 @@ script type="text/template" id="template-no-remaining-session-play-time"
|
|||
.no-remaining-session-play-time
|
||||
p There is no more time left in this session. You can upgrade your plan.
|
||||
|
||||
script type="text/template" id="template-plan-no-record"
|
||||
.no-remaining-session-play-time
|
||||
p Recording is not possible on your current plan. You can upgrade your plan to create recordings.
|
||||
|
||||
script type="text/template" id="template-plan-no-video"
|
||||
.no-remaining-session-play-time
|
||||
p Video is not possible on your current plan. You can upgrade your plan to use video.
|
||||
|
||||
|
||||
script type="text/template" id="template-no-remaining-month-play-time"
|
||||
.no-remaining-monthly-play-time
|
||||
p There is no more session time left for this month. You can upgrade your plan.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
.spinner-large
|
||||
|
||||
h2.create-account-header
|
||||
.badge-number 2
|
||||
.badge-number 3
|
||||
| Download the free JamKazam app
|
||||
|
||||
.download-content
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
.jamtracks
|
||||
|
||||
h2.shop-jamtracks
|
||||
.badge-number 3
|
||||
.badge-number 4
|
||||
| Get your free JamTrack
|
||||
span.special-value
|
||||
| ($2.99 value)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@
|
|||
<% provide(:description, 'Sign up for your JamKazam account to play music with others online in real time and network with musicians.') %>
|
||||
|
||||
<div class="content-wrapper register-page">
|
||||
<h2 class="create-account-header"><div class="badge-number">1</div>Create your free JamKazam account</h2>
|
||||
|
||||
<%= form_for(@user, :url => @signup_postback, :method => :post) do |f| %>
|
||||
|
||||
<div class="register-container">
|
||||
<div class="left-side">
|
||||
|
||||
<h2 class="create-account-header"><div class="badge-number">1</div>Create your free JamKazam account</h2>
|
||||
|
||||
|
||||
<div class="register-container">
|
||||
<div class="register-as">
|
||||
Register as a:
|
||||
<%= f.radio_button :musician, true, :class => "register-as register-musician" %>
|
||||
|
|
@ -22,7 +26,7 @@
|
|||
<%= f.text_field :first_name %>
|
||||
</div>
|
||||
|
||||
<div class="field last_name ]>
|
||||
<div class="field last_name">
|
||||
<%= f.label :last_name, "Last Name:" %>
|
||||
<%= f.text_field :last_name %>
|
||||
</div>
|
||||
|
|
@ -60,19 +64,48 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "CREATE ACCOUNT", class: "button-orange disabled", id:"create-account-submit"%>
|
||||
<%= link_to "CANCEL", root_path, :class=>'button-grey' %>
|
||||
</div>
|
||||
|
||||
<br clear="all"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-side">
|
||||
<h2 class="subscription-type-header"><div class="badge-number">2</div>Select your subscription plan</h2>
|
||||
|
||||
<div class="subscription-options">
|
||||
<div class="radio-field">
|
||||
<%= f.radio_button :desired_plan_code, '', :checked => true %>
|
||||
<label for="male">Free Plan ($0.00/month)</label>
|
||||
</div>
|
||||
<div class="radio-field">
|
||||
<%= f.radio_button :desired_plan_code, 'jamsubsilver'%>
|
||||
<label for="male">Silver Plan ($4.99/month)</label>
|
||||
</div>
|
||||
<div class="radio-field">
|
||||
<%= f.radio_button :desired_plan_code, 'jamsubgold' %>
|
||||
<label for="male">Gold Plan ($9.99/month)</label>
|
||||
</div>
|
||||
<div class="radio-field">
|
||||
<%= f.radio_button :desired_plan_code, 'jamsubplatinum'%>
|
||||
<label for="male">Platinum Plan ($19.99/month)</label>
|
||||
</div>
|
||||
|
||||
<div class="comparison-block">
|
||||
<a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-" target="_blank">compare plans</a>
|
||||
<div class="pricing-info">New users have access to the Gold plan free for the first 30 days. The plan you select will take effect after the first 30-day trial period. Taxes apply to all non-free plans.</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "CREATE ACCOUNT", class: "button-orange disabled", id:"create-account-submit"%>
|
||||
<%= link_to "CANCEL", root_path, :class=>'button-grey' %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<!-- end inner -->
|
||||
|
||||
<!-- end overlay content -->
|
||||
|
|
@ -109,6 +142,15 @@
|
|||
$('#' + value.instrument_id.replace(" ", "") + "_proficiency").val(value.proficiency_level.toString())
|
||||
})
|
||||
}
|
||||
if(window.gon) {
|
||||
var $radios = $('input:radio[name="jam_ruby_user[desired_plan_code]"]');
|
||||
if(gon.plan_code) {
|
||||
$radios.filter(gon.plan_code, true)
|
||||
}
|
||||
else {
|
||||
$radios.filter('[value="<%= @plan_code %>"]').prop('checked', true)
|
||||
}
|
||||
}
|
||||
|
||||
// show signup errors, if any
|
||||
if (window.gon && gon.signup_errors) {
|
||||
|
|
|
|||
|
|
@ -482,6 +482,13 @@ if defined?(Bundler)
|
|||
config.root_redirect_on = true
|
||||
config.root_redirect_subdomain = ''
|
||||
config.root_redirect_path = '/'
|
||||
config.subscription_codes = [{id: 'jamsubsilver', name: 'Silver', price: 4.99}, {id: 'jamsubgold', name: 'Gold', price:9.99}, {id: 'jamsubplatinum', name: 'Platinum', price:19.99}]
|
||||
config.subscription_codes = [
|
||||
{id: nil, name: 'Free', price: 0.00, cycle: 'month'},
|
||||
{id: 'jamsubsilver', name: 'Silver', price: 4.99, cycle: 'month'},
|
||||
{id: 'jamsubgold', name: 'Gold', price:9.99, cycle: 'month'},
|
||||
{id: 'jamsubplatinum', name: 'Platinum', price:19.99, cycle: 'month'},
|
||||
{id: 'jamsubsilveryearly', name: 'Silver', price: 49.99, cycle: 'year'},
|
||||
{id: 'jamsubgoldyearly', name: 'Gold', price:99.99, cycle: 'year'},
|
||||
{id: 'jamsubplatinumyearly', name: 'Platinum', price:199.99, cycle: 'year'}]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -394,8 +394,10 @@ Rails.application.routes.draw do
|
|||
match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete
|
||||
match '/recurly/get_account' => 'api_recurly#get_account', :via => :get
|
||||
match '/recurly/payment_history' => 'api_recurly#payment_history', :via => :get
|
||||
match '/recurly/invoice_history' => 'api_recurly#invoice_history', :via => :get
|
||||
#match '/recurly/get_subscription' => 'api_recurly#get_subscription', :via => :get
|
||||
match '/recurly/update_account' => 'api_recurly#update_account', :via => :put
|
||||
match '/recurly/update_payment' => 'api_recurly#update_payment', :via => :post
|
||||
match '/recurly/billing_info' => 'api_recurly#billing_info', :via => :get
|
||||
match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put
|
||||
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class UserManager < BaseManager
|
|||
license_start = options[:license_start]
|
||||
license_end = options[:license_end]
|
||||
import_source = options[:import_source]
|
||||
desired_plan_code = options[:desired_plan_code]
|
||||
|
||||
recaptcha_failed = false
|
||||
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
|
||||
|
|
@ -112,7 +113,8 @@ class UserManager < BaseManager
|
|||
platform_instructor: platform_instructor,
|
||||
license_start: license_start,
|
||||
license_end: license_end,
|
||||
import_source: import_source)
|
||||
import_source: import_source,
|
||||
desired_plan_code: desired_plan_code)
|
||||
user
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -754,6 +754,7 @@ module JamWebsockets
|
|||
client_id = options["client_id"]
|
||||
reconnect_music_session_id = options["music_session_id"]
|
||||
client_type = options["client_type"]
|
||||
machine_fingerprint = options["machine"]
|
||||
os = options["os"]
|
||||
udp_reachable = options["udp_reachable"].nil? ? true : options["udp_reachable"] == 'true'
|
||||
jamblaster_serial_no = options["jamblaster_serial_no"]
|
||||
|
|
@ -866,7 +867,12 @@ module JamWebsockets
|
|||
|
||||
heartbeat_interval, connection_stale_time, connection_expire_time = determine_connection_times(user, client_type)
|
||||
|
||||
@log.debug "logged in #{user} with client_id: #{client_id}"
|
||||
@log.debug "logged in #{user} with client_id: #{client_id} and fingerprint #{machine_fingerprint}"
|
||||
|
||||
# track fingerprint
|
||||
if machine_fingerprint && user.client_fingerprint != machine_fingerprint
|
||||
user.update_attribute(:client_fingerprint, machine_fingerprint)
|
||||
end
|
||||
|
||||
# check if there's a connection for the client... if it's stale, reconnect it
|
||||
if !connection.nil? && connecting
|
||||
|
|
@ -973,7 +979,8 @@ module JamWebsockets
|
|||
user.name,
|
||||
connection.client_id_int,
|
||||
client_update,
|
||||
arses)
|
||||
arses,
|
||||
user.subscription_rules(false))
|
||||
stats_logged_in
|
||||
send_to_client(client, login_ack)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue