diff --git a/admin/Gemfile b/admin/Gemfile
index bddf8b3c5..cae7f7e72 100644
--- a/admin/Gemfile
+++ b/admin/Gemfile
@@ -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'
diff --git a/admin/Gemfile.lock b/admin/Gemfile.lock
index 4d567f4de..3983afb54 100644
--- a/admin/Gemfile.lock
+++ b/admin/Gemfile.lock
@@ -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)
diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb
index a71f1f13e..d221bc710 100644
--- a/admin/app/admin/jam_ruby_users.rb
+++ b/admin/app/admin/jam_ruby_users.rb
@@ -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
diff --git a/admin/config/application.rb b/admin/config/application.rb
index cffb75506..17dfaaf20 100644
--- a/admin/config/application.rb
+++ b/admin/config/application.rb
@@ -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"
diff --git a/admin/config/environments/development.rb b/admin/config/environments/development.rb
index d64ae9a95..f9895b612 100644
--- a/admin/config/environments/development.rb
+++ b/admin/config/environments/development.rb
@@ -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'
diff --git a/admin/config/initializers/recurly.rb b/admin/config/initializers/recurly.rb
new file mode 100644
index 000000000..dd309256e
--- /dev/null
+++ b/admin/config/initializers/recurly.rb
@@ -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,
+ #...
+}
diff --git a/db/up/find_sessions_2020.sql b/db/up/find_sessions_2020.sql
index b8083a598..9b11418c4 100644
--- a/db/up/find_sessions_2020.sql
+++ b/db/up/find_sessions_2020.sql
@@ -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);
diff --git a/db/up/scores_better_test_data.sql b/db/up/scores_better_test_data.sql
index 0f5935cc6..308a1d0f2 100644
--- a/db/up/scores_better_test_data.sql
+++ b/db/up/scores_better_test_data.sql
@@ -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(
diff --git a/db/up/scores_create_schemas_and_extensions.sql b/db/up/scores_create_schemas_and_extensions.sql
index a4261ccbd..226511ce4 100644
--- a/db/up/scores_create_schemas_and_extensions.sql
+++ b/db/up/scores_create_schemas_and_extensions.sql
@@ -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;
diff --git a/db/up/scores_create_tables.sql b/db/up/scores_create_tables.sql
index 4dfad4cc6..2c7f516d5 100644
--- a/db/up/scores_create_tables.sql
+++ b/db/up/scores_create_tables.sql
@@ -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);
diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto
index 74b1ea13d..7df51394f 100644
--- a/pb/src/client_container.proto
+++ b/pb/src/client_container.proto
@@ -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;
diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb
index f4d9844ec..ef8446ca8 100644
--- a/ruby/lib/jam_ruby/message_factory.rb
+++ b/ruby/lib/jam_ruby/message_factory.rb
@@ -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(
diff --git a/ruby/lib/jam_ruby/models/sale.rb b/ruby/lib/jam_ruby/models/sale.rb
index 2674a6d18..73771a19e 100644
--- a/ruby/lib/jam_ruby/models/sale.rb
+++ b/ruby/lib/jam_ruby/models/sale.rb
@@ -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
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 6608ab11e..0d38beaab 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -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
diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb
index d4d56f03e..de88cf060 100644
--- a/ruby/lib/jam_ruby/recurly_client.rb
+++ b/ruby/lib/jam_ruby/recurly_client.rb
@@ -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)
diff --git a/ruby/lib/jam_ruby/subscription_definitions.rb b/ruby/lib/jam_ruby/subscription_definitions.rb
index f7e6a4a50..b1410fef5 100644
--- a/ruby/lib/jam_ruby/subscription_definitions.rb
+++ b/ruby/lib/jam_ruby/subscription_definitions.rb
@@ -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}"
diff --git a/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb b/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb
new file mode 100644
index 000000000..9efdce26c
--- /dev/null
+++ b/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb
@@ -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
diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js
index c1ff6e153..52c8b2ee5 100644
--- a/web/app/assets/javascripts/JamServer.js
+++ b/web/app/assets/javascripts/JamServer.js
@@ -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);
diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js
index cfc1507aa..c515d9d46 100644
--- a/web/app/assets/javascripts/fakeJamClient.js
+++ b/web/app/assets/javascripts/fakeJamClient.js
@@ -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";
};
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index de1fbbf72..cb17f10da 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -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);
diff --git a/web/app/assets/javascripts/landing/signup.js b/web/app/assets/javascripts/landing/signup.js
index 0eec67cf4..780e4c64a 100644
--- a/web/app/assets/javascripts/landing/signup.js
+++ b/web/app/assets/javascripts/landing/signup.js
@@ -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
diff --git a/web/app/assets/javascripts/react-components/AccountPaymentHistoryScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/AccountPaymentHistoryScreen.js.jsx.coffee
index 1729087f9..dac2fb1c6 100644
--- a/web/app/assets/javascripts/react-components/AccountPaymentHistoryScreen.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/AccountPaymentHistoryScreen.js.jsx.coffee
@@ -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
`
+
+ 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.
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 = [``]
+ for countryId, countryInfo of @state.countries
+ countries.push(``)
+
+ country = @state.countries[this.currentCountry()]
+ else
+ countries = []
+
+ countryJsx = `
+ `
+
+
if @state.uncollectables.length > 0
uncollectable = @state.uncollectables[0]
uncollectableMessage = `