Merge branch 'develop' into feature/jam_track_analytics
|
|
@ -2,12 +2,9 @@ ActiveAdmin.register_page "Recurly Health" do
|
|||
menu :parent => 'Misc'
|
||||
|
||||
content :title => "Recurly Transaction Totals" do
|
||||
table_for Sale.check_integrity do
|
||||
table_for Sale.check_integrity_of_jam_track_sales do
|
||||
column "Total", :total
|
||||
column "Unknown", :not_known
|
||||
column "Successes", :succeeded
|
||||
column "Failures", :failed
|
||||
column "Refunds", :refunded
|
||||
column "Voids", :voided
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -273,4 +273,6 @@ drop_position_unique_jam_track.sql
|
|||
recording_client_metadata.sql
|
||||
preview_support_mp3.sql
|
||||
jam_track_duration.sql
|
||||
sales.sql
|
||||
sales.sql
|
||||
show_whats_next_count.sql
|
||||
recurly_adjustments.sql
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
ALTER TABLE sale_line_items ADD COLUMN recurly_adjustment_uuid VARCHAR(500);
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_adjustment_credit_uuid VARCHAR(500);
|
||||
ALTER TABLE jam_track_rights ADD COLUMN recurly_adjustment_uuid VARCHAR(500);
|
||||
ALTER TABLE jam_track_rights ADD COLUMN recurly_adjustment_credit_uuid VARCHAR(500);
|
||||
ALTER TABLE sales ADD COLUMN recurly_invoice_id VARCHAR(500) UNIQUE;
|
||||
ALTER TABLE sales ADD COLUMN recurly_invoice_number INTEGER;
|
||||
|
||||
ALTER TABLE sales ADD COLUMN recurly_subtotal_in_cents INTEGER;
|
||||
ALTER TABLE sales ADD COLUMN recurly_tax_in_cents INTEGER;
|
||||
ALTER TABLE sales ADD COLUMN recurly_total_in_cents INTEGER;
|
||||
ALTER TABLE sales ADD COLUMN recurly_currency VARCHAR;
|
||||
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_tax_in_cents INTEGER;
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_total_in_cents INTEGER;
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_currency VARCHAR;
|
||||
ALTER TABLE sale_line_items ADD COLUMN recurly_discount_in_cents INTEGER;
|
||||
|
||||
ALTER TABLE sales ADD COLUMN sale_type VARCHAR NOT NULL;
|
||||
|
||||
ALTER TABLE recurly_transaction_web_hooks ALTER COLUMN subscription_id DROP NOT NULL;
|
||||
|
||||
CREATE INDEX recurly_transaction_web_hooks_invoice_id_ndx ON recurly_transaction_web_hooks(invoice_id);
|
||||
|
||||
ALTER TABLE jam_track_rights DROP COLUMN recurly_subscription_uuid;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN show_whats_next_count INTEGER NOT NULL DEFAULT 0;
|
||||
|
|
@ -69,6 +69,7 @@ require "jam_ruby/connection_manager"
|
|||
require "jam_ruby/version"
|
||||
require "jam_ruby/environment"
|
||||
require "jam_ruby/init"
|
||||
require "jam_ruby/app/mailers/admin_mailer"
|
||||
require "jam_ruby/app/mailers/user_mailer"
|
||||
require "jam_ruby/app/mailers/invited_user_mailer"
|
||||
require "jam_ruby/app/mailers/corp_mailer"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
module JamRuby
|
||||
# sends out a boring ale
|
||||
class AdminMailer < ActionMailer::Base
|
||||
include SendGrid
|
||||
|
||||
|
||||
DEFAULT_SENDER = "JamKazam <noreply@jamkazam.com>"
|
||||
|
||||
default :from => DEFAULT_SENDER
|
||||
|
||||
sendgrid_category :use_subject_lines
|
||||
#sendgrid_enable :opentrack, :clicktrack # this makes our emails creepy, imo (seth)
|
||||
sendgrid_unique_args :env => Environment.mode
|
||||
|
||||
def alerts(options)
|
||||
mail(to: APP_CONFIG.email_alerts_alias,
|
||||
body: options[:body],
|
||||
content_type: "text/plain",
|
||||
subject: options[:subject])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<td align="left">
|
||||
|
||||
<!-- CALL OUT BOX -->
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/account/profile">here to unsubscribe</a> and update your profile settings.
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #ffcc00;" href="http://www.jamkazam.com/unsubscribe/#{@user.unsubscribe_token}">here to unsubscribe</a> and update your profile settings.
|
||||
</font></p>
|
||||
</td></tr></table>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<% end %>
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. Visit your profile page to unsubscribe: http://www.jamkazam.com/client#/account/profile.
|
||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. Visit your profile page to unsubscribe: http://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
|
|||
|
|
@ -614,7 +614,8 @@ module JamRuby
|
|||
def synchronize_recurly(jam_track)
|
||||
begin
|
||||
recurly = RecurlyClient.new
|
||||
recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track)
|
||||
# no longer create JamTrack plans: VRFS-3028
|
||||
# recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track)
|
||||
rescue RecurlyClientError => x
|
||||
finish('recurly_create_plan', x.errors.to_s)
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@ module JamRuby
|
|||
validates :metronome_open, :inclusion => {:in => [true, false]}
|
||||
validates :as_musician, :inclusion => {:in => [true, false, nil]}
|
||||
validates :client_type, :inclusion => {:in => CLIENT_TYPES}
|
||||
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
|
||||
validates_numericality_of :last_jam_audio_latency, greater_than: 0, :allow_nil => true
|
||||
validate :can_join_music_session, :if => :joining_session?
|
||||
validate :user_or_latency_tester_present
|
||||
|
||||
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||
# this is no longer required with the new no-input profile
|
||||
#after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||
after_create :did_create
|
||||
after_save :report_add_participant
|
||||
|
||||
|
|
@ -62,11 +63,11 @@ module JamRuby
|
|||
def state_message
|
||||
case self.aasm_state.to_sym
|
||||
when CONNECT_STATE
|
||||
'Connected'
|
||||
when STALE_STATE
|
||||
'Stale'
|
||||
'Connected'
|
||||
when STALE_STATE
|
||||
'Stale'
|
||||
else
|
||||
'Idle'
|
||||
'Idle'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ module JamRuby
|
|||
def joining_session?
|
||||
joining_session
|
||||
end
|
||||
|
||||
|
||||
def can_join_music_session
|
||||
|
||||
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?
|
||||
|
|
@ -183,8 +184,8 @@ module JamRuby
|
|||
end
|
||||
|
||||
def associate_tracks(tracks)
|
||||
self.tracks.clear()
|
||||
unless tracks.nil?
|
||||
self.tracks.clear()
|
||||
tracks.each do |track|
|
||||
t = Track.new
|
||||
t.instrument = Instrument.find(track["instrument_id"])
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module JamRuby
|
|||
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
|
||||
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
|
||||
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
|
||||
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, as: :admin
|
||||
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
|
||||
|
||||
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
|
||||
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
|
||||
|
|
@ -199,5 +199,6 @@ module JamRuby
|
|||
def right_for_user(user)
|
||||
jam_track_rights.where("user_id=?", user).first
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ module JamRuby
|
|||
session_user_history.music_session_id = music_session_id
|
||||
session_user_history.user_id = user_id
|
||||
session_user_history.client_id = client_id
|
||||
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|")
|
||||
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|") if tracks
|
||||
session_user_history.save
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,33 @@ module JamRuby
|
|||
class RecurlyTransactionWebHook < ActiveRecord::Base
|
||||
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
belongs_to :sale_line_item, class_name: 'JamRuby::SaleLineItem', foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid', inverse_of: :recurly_transactions
|
||||
belongs_to :sale, class_name: 'JamRuby::Sale', foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id', inverse_of: :recurly_transactions
|
||||
|
||||
validates :recurly_transaction_id, presence: true
|
||||
validates :subscription_id, presence: true
|
||||
validates :action, presence: true
|
||||
validates :status, presence: true
|
||||
validates :amount_in_cents, numericality: {only_integer: true}
|
||||
validates :user, presence: true
|
||||
|
||||
|
||||
SUCCESSFUL_PAYMENT = 'payment'
|
||||
FAILED_PAYMENT = 'failed_payment'
|
||||
REFUND = 'refund'
|
||||
VOID = 'void'
|
||||
|
||||
def is_credit_type?
|
||||
transaction_type == REFUND || transaction_type == VOID
|
||||
end
|
||||
|
||||
def is_voided?
|
||||
transaction_type == VOID
|
||||
end
|
||||
|
||||
def is_refund?
|
||||
transaction_type == REFUND
|
||||
end
|
||||
|
||||
def self.is_transaction_web_hook?(document)
|
||||
|
||||
return false if document.root.nil?
|
||||
|
|
@ -68,8 +82,42 @@ module JamRuby
|
|||
# now that we have the transaction saved, we also need to delete the jam_track_right if this is a refund, or voided
|
||||
|
||||
if transaction.transaction_type == 'refund' || transaction.transaction_type == 'void'
|
||||
right = JamTrackRight.find_by_recurly_subscription_uuid(transaction.subscription_id)
|
||||
right.destroy if right
|
||||
sale = Sale.find_by_recurly_invoice_id(transaction.invoice_id)
|
||||
|
||||
if sale && sale.is_jam_track_sale?
|
||||
if sale.sale_line_items.length == 1
|
||||
if sale.recurly_total_in_cents == transaction.amount_in_cents
|
||||
jam_track = sale.sale_line_items[0].product
|
||||
jam_track_right = jam_track.right_for_user(transaction.user) if jam_track
|
||||
if jam_track_right
|
||||
jam_track_right.destroy
|
||||
AdminMailer.alerts({
|
||||
subject:"NOTICE: #{transaction.user.email} has had JamTrack: #{jam_track.name} revoked",
|
||||
body: "A void event came from Recurly for sale with Recurly invoice ID #{sale.recurly_invoice_id}. We deleted their right to the track in our own database as a result."
|
||||
}).deliver
|
||||
else
|
||||
AdminMailer.alerts({
|
||||
subject:"NOTICE: #{transaction.user.email} got a refund, but unable to find JamTrackRight to delete",
|
||||
body: "This should just mean the user already has no rights to the JamTrackRight when the refund came in. Not a big deal, but sort of weird..."
|
||||
}).deliver
|
||||
end
|
||||
|
||||
else
|
||||
AdminMailer.alerts({
|
||||
subject:"ACTION REQUIRED: #{transaction.user.email} got a refund it was not for total value of a JamTrack sale",
|
||||
body: "We received a refund notice for an amount that was not the same as the original sale. So, no action was taken in the database. sale total: #{sale.recurly_total_in_cents}, refund amount: #{transaction.amount_in_cents}"
|
||||
}).deliver
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
AdminMailer.alerts({
|
||||
subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice with multiple JamTracks",
|
||||
body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks"
|
||||
}).deliver
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
transaction
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,30 +3,341 @@ module JamRuby
|
|||
# a sale is created every time someone tries to buy something
|
||||
class Sale < ActiveRecord::Base
|
||||
|
||||
JAMTRACK_SALE = 'jamtrack'
|
||||
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
has_many :sale_line_items, class_name: 'JamRuby::SaleLineItem'
|
||||
|
||||
validates :order_total, numericality: { only_integer: false }
|
||||
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale, foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id'
|
||||
|
||||
validates :order_total, numericality: {only_integer: false}
|
||||
validates :user, presence: true
|
||||
|
||||
def self.create(user)
|
||||
@@log = Logging.logger[Sale]
|
||||
|
||||
def self.index(user, params = {})
|
||||
|
||||
limit = params[:per_page]
|
||||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
query = Sale.limit(limit)
|
||||
.includes([:recurly_transactions, :sale_line_items])
|
||||
.where('sales.user_id' => user.id)
|
||||
.order('sales.created_at DESC')
|
||||
|
||||
current_page = params[:page].nil? ? 1 : params[:page].to_i
|
||||
next_page = current_page + 1
|
||||
|
||||
# will_paginate gem
|
||||
query = query.paginate(:page => current_page, :per_page => limit)
|
||||
|
||||
if query.length == 0 # no more results
|
||||
{ query: query, next_page: nil}
|
||||
elsif query.length < limit # no more results
|
||||
{ query: query, next_page: nil}
|
||||
else
|
||||
{ query: query, next_page: next_page }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def state
|
||||
original_total = self.recurly_total_in_cents
|
||||
|
||||
is_voided = false
|
||||
refund_total = 0
|
||||
|
||||
recurly_transactions.each do |transaction|
|
||||
if transaction.is_voided?
|
||||
is_voided = true
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
if transaction.is_refund?
|
||||
refund_total = refund_total + transaction.amount_in_cents
|
||||
end
|
||||
end
|
||||
|
||||
# if refund_total is > 0, then you have a refund.
|
||||
# if voided is true, then in theory the whole thing has been refunded
|
||||
{
|
||||
voided: is_voided,
|
||||
original_total: original_total,
|
||||
refund_total: refund_total
|
||||
}
|
||||
end
|
||||
|
||||
def self.preview_invoice(current_user, shopping_carts)
|
||||
|
||||
line_items = {jam_tracks: []}
|
||||
shopping_carts_jam_tracks = []
|
||||
shopping_carts_subscriptions = []
|
||||
shopping_carts.each do |shopping_cart|
|
||||
|
||||
if shopping_cart.is_jam_track?
|
||||
shopping_carts_jam_tracks << shopping_cart
|
||||
else
|
||||
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
|
||||
shopping_carts_subscriptions << shopping_cart
|
||||
end
|
||||
end
|
||||
|
||||
jam_track_items = preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks)
|
||||
line_items[:jam_tracks] = jam_track_items if jam_track_items
|
||||
|
||||
# TODO: process shopping_carts_subscriptions
|
||||
|
||||
line_items
|
||||
end
|
||||
|
||||
# place_order will create one or more sales based on the contents of shopping_carts for the current user
|
||||
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
|
||||
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
|
||||
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
|
||||
def self.place_order(current_user, shopping_carts)
|
||||
|
||||
sales = []
|
||||
shopping_carts_jam_tracks = []
|
||||
shopping_carts_subscriptions = []
|
||||
shopping_carts.each do |shopping_cart|
|
||||
|
||||
if shopping_cart.is_jam_track?
|
||||
shopping_carts_jam_tracks << shopping_cart
|
||||
else
|
||||
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
|
||||
shopping_carts_subscriptions << shopping_cart
|
||||
end
|
||||
end
|
||||
|
||||
jam_track_sale = order_jam_tracks(current_user, shopping_carts_jam_tracks)
|
||||
sales << jam_track_sale if jam_track_sale
|
||||
|
||||
# TODO: process shopping_carts_subscriptions
|
||||
|
||||
sales
|
||||
end
|
||||
|
||||
def self.preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks)
|
||||
### XXX TODO;
|
||||
|
||||
# we currently use a fake plan in Recurly to estimate taxes using the Pricing.Attach metod in Recurly.js
|
||||
|
||||
# if we were to implement this the right way (ensure adjustments are on the account as necessary), then it would be better (more correct)
|
||||
# just a pain to implement
|
||||
end
|
||||
|
||||
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
|
||||
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
|
||||
def self.order_jam_tracks(current_user, shopping_carts_jam_tracks)
|
||||
|
||||
client = RecurlyClient.new
|
||||
|
||||
sale = nil
|
||||
Sale.transaction do
|
||||
sale = create_jam_track_sale(current_user)
|
||||
|
||||
if sale.valid?
|
||||
account = client.get_account(current_user)
|
||||
if account.present?
|
||||
|
||||
purge_pending_adjustments(account)
|
||||
|
||||
created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
|
||||
|
||||
# now invoice the sale ... almost done
|
||||
|
||||
begin
|
||||
invoice = account.invoice!
|
||||
sale.recurly_invoice_id = invoice.uuid
|
||||
sale.recurly_invoice_number = invoice.invoice_number
|
||||
|
||||
# now slap in all the real tax/purchase totals
|
||||
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
|
||||
sale.recurly_tax_in_cents = invoice.tax_in_cents
|
||||
sale.recurly_total_in_cents = invoice.total_in_cents
|
||||
sale.recurly_currency = invoice.currency
|
||||
|
||||
# and resolve against sale_line_items
|
||||
sale.sale_line_items.each do |sale_line_item|
|
||||
found_line_item = false
|
||||
invoice.line_items.each do |line_item|
|
||||
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
|
||||
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
|
||||
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
|
||||
sale_line_item.recurly_currency = line_item.currency
|
||||
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
|
||||
found_line_item = true
|
||||
break
|
||||
end
|
||||
|
||||
if !found_line_item
|
||||
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
|
||||
puts "CANT FIND LINE ITEM"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
unless sale.save
|
||||
raise RecurlyClientError, "Invalid sale (at end)."
|
||||
end
|
||||
rescue Recurly::Resource::Invalid => e
|
||||
# this exception is thrown by invoice! if the invoice is invalid
|
||||
sale.rollback_adjustments(current_user, created_adjustments)
|
||||
sale = nil
|
||||
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to place order."
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Invalid sale."
|
||||
end
|
||||
end
|
||||
sale
|
||||
end
|
||||
|
||||
def process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
|
||||
|
||||
created_adjustments = []
|
||||
|
||||
begin
|
||||
shopping_carts_jam_tracks.each do |shopping_cart|
|
||||
process_jam_track(current_user, shopping_cart, account, created_adjustments)
|
||||
end
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
# rollback any adjustments created if error
|
||||
rollback_adjustments(user, created_adjustments)
|
||||
raise RecurlyClientError, x.to_s
|
||||
rescue Exception => e
|
||||
# rollback any adjustments created if error
|
||||
rollback_adjustments(user, created_adjustments)
|
||||
raise e
|
||||
end
|
||||
|
||||
created_adjustments
|
||||
end
|
||||
|
||||
|
||||
def process_jam_track(current_user, shopping_cart, account, created_adjustments)
|
||||
recurly_adjustment_uuid = nil
|
||||
recurly_adjustment_credit_uuid = nil
|
||||
|
||||
# we do this because of ShoppingCart.remove_jam_track_from_cart; if it occurs, which should be rare, we need fresh shopping cart info
|
||||
shopping_cart.reload
|
||||
|
||||
# get the JamTrack in this shopping cart
|
||||
jam_track = shopping_cart.cart_product
|
||||
|
||||
if jam_track.right_for_user(current_user)
|
||||
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
|
||||
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
|
||||
ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart)
|
||||
return
|
||||
end
|
||||
|
||||
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
|
||||
adjustments = shopping_cart.create_adjustment_attributes(current_user)
|
||||
|
||||
adjustments.each do |adjustment|
|
||||
|
||||
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
|
||||
created_adjustment = account.adjustments.new(adjustment)
|
||||
created_adjustment.save
|
||||
|
||||
# if the adjustment could not be made, bail
|
||||
raise RecurlyClientError.new(created_adjustment.errors) if created_adjustment.errors.any?
|
||||
|
||||
# keep track of adjustments we created for this order, in case we have to roll them back
|
||||
created_adjustments << created_adjustment
|
||||
|
||||
if ShoppingCart.is_product_purchase?(adjustment)
|
||||
# this was a normal product adjustment, so track it as such
|
||||
recurly_adjustment_uuid = created_adjustment.uuid
|
||||
else
|
||||
# this was a 'credit' adjustment, so track it as such
|
||||
recurly_adjustment_credit_uuid = created_adjustment.uuid
|
||||
end
|
||||
end
|
||||
|
||||
# create one sale line item for every jam track
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(self, shopping_cart, nil, recurly_adjustment_uuid, recurly_adjustment_credit_uuid)
|
||||
|
||||
# if the sale line item is invalid, blow up the transaction
|
||||
unless sale_line_item.valid?
|
||||
@log.error("sale item invalid! #{sale_line_item.errors.inspect}")
|
||||
puts("sale item invalid! #{sale_line_item.errors.inspect}")
|
||||
Stats.write('web.recurly.purchase.sale_invalid', {message: sale_line_item.errors.to_s, value: 1})
|
||||
raise RecurlyClientError.new(sale_line_item.errors)
|
||||
end
|
||||
|
||||
# create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident)
|
||||
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
|
||||
jam_track_right.redeemed = shopping_cart.free?
|
||||
end
|
||||
|
||||
# also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks
|
||||
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false) if shopping_cart.free?
|
||||
|
||||
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
|
||||
if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid
|
||||
jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid
|
||||
jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
|
||||
unless jam_track_right.save
|
||||
raise RecurlyClientError.new(jam_track_right.errors)
|
||||
end
|
||||
end
|
||||
|
||||
# delete the shopping cart; it's been dealt with
|
||||
shopping_cart.destroy if shopping_cart
|
||||
|
||||
# blow up the transaction if the JamTrackRight did not get created
|
||||
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
|
||||
end
|
||||
|
||||
|
||||
def rollback_adjustments(current_user, adjustments)
|
||||
begin
|
||||
adjustments.each { |adjustment| adjustment.destroy }
|
||||
rescue Exception => e
|
||||
AdminMailer.alerts({
|
||||
subject: "ACTION REQUIRED: #{current_user.email} did not have all of his adjustments destroyed in rollback",
|
||||
body: "go delete any adjustments on the account that don't belong. error: #{e}\n\nAdjustments: #{adjustments.inspect}"
|
||||
}).deliver
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def self.purge_pending_adjustments(account)
|
||||
account.adjustments.pending.find_each do |adjustment|
|
||||
# we only pre-emptively destroy pending adjustments if they appear to be created by the server
|
||||
adjustment.destroy if ShoppingCart.is_server_pending_adjustment?(adjustment)
|
||||
end
|
||||
end
|
||||
|
||||
def is_jam_track_sale?
|
||||
sale_type == JAMTRACK_SALE
|
||||
end
|
||||
|
||||
def self.create_jam_track_sale(user)
|
||||
sale = Sale.new
|
||||
sale.user = user
|
||||
sale.sale_type = JAMTRACK_SALE
|
||||
sale.order_total = 0
|
||||
sale.save
|
||||
sale
|
||||
end
|
||||
|
||||
def self.check_integrity
|
||||
SaleLineItem.select([:total, :not_known, :succeeded, :failed, :refunded, :voided]).find_by_sql(
|
||||
"SELECT COUNT(sale_line_items.id) AS total,
|
||||
COUNT(CASE WHEN transactions.id IS NULL THEN 1 ELSE null END) not_known,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT}' THEN 1 ELSE null END) succeeded,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::FAILED_PAYMENT}' THEN 1 ELSE null END) failed,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::REFUND}' THEN 1 ELSE null END) refunded,
|
||||
# this checks just jamtrack sales appropriately
|
||||
def self.check_integrity_of_jam_track_sales
|
||||
Sale.select([:total, :voided]).find_by_sql(
|
||||
"SELECT COUNT(sales.id) AS total,
|
||||
COUNT(CASE WHEN transactions.transaction_type = '#{RecurlyTransactionWebHook::VOID}' THEN 1 ELSE null END) voided
|
||||
FROM sale_line_items
|
||||
LEFT OUTER JOIN recurly_transaction_web_hooks as transactions ON subscription_id = recurly_subscription_uuid")
|
||||
FROM sales
|
||||
LEFT OUTER JOIN recurly_transaction_web_hooks as transactions ON invoice_id = sales.recurly_invoice_id
|
||||
WHERE sale_type = '#{JAMTRACK_SALE}'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
module JamRuby
|
||||
class SaleLineItem < ActiveRecord::Base
|
||||
|
||||
belongs_to :sale, class_name: 'JamRuby::Sale'
|
||||
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
|
||||
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
|
||||
|
||||
JAMBLASTER = 'JamBlaster'
|
||||
JAMCLOUD = 'JamCloud'
|
||||
JAMTRACK = 'JamTrack'
|
||||
|
||||
belongs_to :sale, class_name: 'JamRuby::Sale'
|
||||
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
|
||||
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
|
||||
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
|
||||
|
||||
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK]}
|
||||
validates :unit_price, numericality: {only_integer: false}
|
||||
validates :quantity, numericality: {only_integer: true}
|
||||
|
|
@ -18,10 +19,50 @@ module JamRuby
|
|||
validates :recurly_plan_code, presence:true
|
||||
validates :sale, presence:true
|
||||
|
||||
def self.create_from_shopping_cart(sale, shopping_cart, recurly_subscription_uuid)
|
||||
def product
|
||||
if product_type == JAMTRACK
|
||||
JamTrack.find_by_id(product_id)
|
||||
else
|
||||
raise 'unsupported product type'
|
||||
end
|
||||
end
|
||||
|
||||
def product_info
|
||||
item = product
|
||||
{ name: product.name } if item
|
||||
end
|
||||
|
||||
def state
|
||||
voided = false
|
||||
refunded = false
|
||||
failed = false
|
||||
succeeded = false
|
||||
|
||||
recurly_transactions.each do |transaction|
|
||||
if transaction.transaction_type == RecurlyTransactionWebHook::VOID
|
||||
voided = true
|
||||
elsif transaction.transaction_type == RecurlyTransactionWebHook::REFUND
|
||||
refunded = true
|
||||
elsif transaction.transaction_type == RecurlyTransactionWebHook::FAILED_PAYMENT
|
||||
failed = true
|
||||
elsif transaction.transaction_type == RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT
|
||||
succeeded = true
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
void: voided,
|
||||
refund: refunded,
|
||||
fail: failed,
|
||||
success: succeeded
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def self.create_from_shopping_cart(sale, shopping_cart, recurly_subscription_uuid, recurly_adjustment_uuid, recurly_adjustment_credit_uuid)
|
||||
product_info = shopping_cart.product_info
|
||||
|
||||
sale.order_total = sale.order_total + product_info[:total_price]
|
||||
sale.order_total = sale.order_total + product_info[:real_price]
|
||||
|
||||
sale_line_item = SaleLineItem.new
|
||||
sale_line_item.product_type = shopping_cart.cart_type
|
||||
|
|
@ -33,7 +74,9 @@ module JamRuby
|
|||
sale_line_item.recurly_plan_code = product_info[:plan_code]
|
||||
sale_line_item.product_id = shopping_cart.cart_id
|
||||
sale_line_item.recurly_subscription_uuid = recurly_subscription_uuid
|
||||
sale_line_item.sale = sale
|
||||
sale_line_item.recurly_adjustment_uuid = recurly_adjustment_uuid
|
||||
sale_line_item.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
|
||||
sale.sale_line_items << sale_line_item
|
||||
sale_line_item.save
|
||||
sale_line_item
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
module JamRuby
|
||||
class ShoppingCart < ActiveRecord::Base
|
||||
|
||||
# just a normal purchase; used on the description field of a recurly adjustment
|
||||
PURCHASE_NORMAL = 'purchase-normal'
|
||||
# a free purchase; used on the description field of a recurly adjustment
|
||||
PURCHASE_FREE = 'purchase-free'
|
||||
# a techinicality of Recurly; we create a free-credit adjustment to balance out the free purchase adjustment
|
||||
PURCHASE_FREE_CREDIT = 'purchase-free-credit'
|
||||
|
||||
PURCHASE_REASONS = [PURCHASE_NORMAL, PURCHASE_FREE, PURCHASE_FREE_CREDIT]
|
||||
|
||||
attr_accessible :quantity, :cart_type, :product_info
|
||||
|
||||
validates_uniqueness_of :cart_id, scope: :cart_type
|
||||
|
||||
belongs_to :user, :inverse_of => :shopping_carts, :class_name => "JamRuby::User", :foreign_key => "user_id"
|
||||
|
||||
validates :cart_id, presence: true
|
||||
|
|
@ -14,14 +25,20 @@ module JamRuby
|
|||
|
||||
def product_info
|
||||
product = self.cart_product
|
||||
{name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem} unless product.nil?
|
||||
{name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem} unless product.nil?
|
||||
end
|
||||
|
||||
# multiply quantity by price
|
||||
def total_price(product)
|
||||
quantity * product.price
|
||||
end
|
||||
|
||||
# multiply (quantity - redeemable) by price
|
||||
def total_price(product)
|
||||
def real_price(product)
|
||||
(quantity - marked_for_redeem) * product.price
|
||||
end
|
||||
|
||||
|
||||
def cart_product
|
||||
self.cart_class_name.classify.constantize.find_by_id(self.cart_id) unless self.cart_class_name.blank?
|
||||
end
|
||||
|
|
@ -51,6 +68,59 @@ module JamRuby
|
|||
cart
|
||||
end
|
||||
|
||||
def is_jam_track?
|
||||
cart_type == JamTrack::PRODUCT_TYPE
|
||||
end
|
||||
|
||||
|
||||
# returns an array of adjustments for the shopping cart
|
||||
def create_adjustment_attributes(current_user)
|
||||
raise "not a jam track" unless is_jam_track?
|
||||
|
||||
info = self.product_info
|
||||
|
||||
if free?
|
||||
|
||||
# create the credit, then the pseudo charge
|
||||
[
|
||||
{
|
||||
accounting_code: PURCHASE_FREE_CREDIT,
|
||||
currency: 'USD',
|
||||
unit_amount_in_cents: -(info[:total_price] * 100).to_i,
|
||||
description: "JamTrack: " + info[:name] + " (Credit)",
|
||||
tax_exempt: true
|
||||
},
|
||||
{
|
||||
accounting_code: PURCHASE_FREE,
|
||||
currency: 'USD',
|
||||
unit_amount_in_cents: (info[:total_price] * 100).to_i,
|
||||
description: "JamTrack: " + info[:name],
|
||||
tax_exempt: true
|
||||
}
|
||||
]
|
||||
else
|
||||
[
|
||||
{
|
||||
accounting_code: PURCHASE_NORMAL,
|
||||
currency: 'USD',
|
||||
unit_amount_in_cents: (info[:total_price] * 100).to_i,
|
||||
description: "JamTrack: " + info[:name],
|
||||
tax_exempt: false
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def self.is_product_purchase?(adjustment)
|
||||
(adjustment[:accounting_code].include?(PURCHASE_FREE) || adjustment[:accounting_code].include?(PURCHASE_NORMAL)) && !adjustment[:accounting_code].include?(PURCHASE_FREE_CREDIT)
|
||||
end
|
||||
|
||||
# recurly_adjustment is a Recurly::Adjustment (http://www.rubydoc.info/gems/recurly/Recurly/Adjustment)
|
||||
# this asks, 'is this a pending adjustment?' AND 'was this adjustment created by the server (vs manually by someone -- we should leave those alone).'
|
||||
def self.is_server_pending_adjustment?(recurly_adjustment)
|
||||
recurly_adjustment.state == 'pending' && (recurly_adjustment.accounting_code.include?(PURCHASE_FREE) || recurly_adjustment.accounting_code.include?(PURCHASE_NORMAL) || recurly_adjustment.accounting_code.include?(PURCHASE_FREE_CREDIT))
|
||||
end
|
||||
|
||||
# if the user has a redeemable jam_track still on their account, then also check if any shopping carts have already been marked.
|
||||
# if no shpping carts have been marked, then mark it redeemable
|
||||
# should be wrapped in a TRANSACTION
|
||||
|
|
@ -73,20 +143,8 @@ module JamRuby
|
|||
def self.add_jam_track_to_cart(any_user, jam_track)
|
||||
cart = nil
|
||||
ShoppingCart.transaction do
|
||||
# does this user already have this JamTrack in their cart? If so, don't add it.
|
||||
|
||||
duplicate_found = false
|
||||
any_user.shopping_carts.each do |shopping_cart|
|
||||
if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE && shopping_cart.cart_id == jam_track.id
|
||||
duplicate_found = true
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
unless duplicate_found
|
||||
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
|
||||
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
|
||||
end
|
||||
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
|
||||
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
|
||||
end
|
||||
cart
|
||||
end
|
||||
|
|
|
|||
|
|
@ -375,6 +375,10 @@ module JamRuby
|
|||
self.purchased_jam_tracks.count
|
||||
end
|
||||
|
||||
def sales_count
|
||||
self.sales.count
|
||||
end
|
||||
|
||||
def joined_score
|
||||
return nil unless has_attribute?(:score)
|
||||
a = read_attribute(:score)
|
||||
|
|
@ -1533,6 +1537,28 @@ module JamRuby
|
|||
ShoppingCart.where("user_id=?", self).destroy_all
|
||||
end
|
||||
|
||||
def unsubscribe_token
|
||||
self.class.create_access_token(self)
|
||||
end
|
||||
|
||||
# Verifier based on our application secret
|
||||
def self.verifier
|
||||
ActiveSupport::MessageVerifier.new(APP_CONFIG.secret_token)
|
||||
end
|
||||
|
||||
# Get a user from a token
|
||||
def self.read_access_token(signature)
|
||||
uid = self.verifier.verify(signature)
|
||||
User.find_by_id uid
|
||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||
nil
|
||||
end
|
||||
|
||||
# Class method for token generation
|
||||
def self.create_access_token(user)
|
||||
verifier.generate(user.id)
|
||||
end
|
||||
|
||||
private
|
||||
def create_remember_token
|
||||
self.remember_token = SecureRandom.urlsafe_base64
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
require 'recurly'
|
||||
module JamRuby
|
||||
class RecurlyClient
|
||||
class RecurlyClient
|
||||
def initialize()
|
||||
@log = Logging.logger[self]
|
||||
end
|
||||
|
|
@ -11,37 +11,37 @@ module JamRuby
|
|||
begin
|
||||
#puts "Recurly.api_key: #{Recurly.api_key}"
|
||||
account = Recurly::Account.create(options)
|
||||
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
#puts "Error: #{x} : #{Kernel.caller}"
|
||||
raise RecurlyClientError, x.to_s
|
||||
else
|
||||
if account
|
||||
current_user.update_attribute(:recurly_code, account.account_code)
|
||||
end
|
||||
end
|
||||
account
|
||||
current_user.update_attribute(:recurly_code, account.account_code)
|
||||
end
|
||||
end
|
||||
account
|
||||
end
|
||||
|
||||
def has_account?(current_user)
|
||||
account = get_account(current_user)
|
||||
account = get_account(current_user)
|
||||
!!account
|
||||
end
|
||||
|
||||
def delete_account(current_user)
|
||||
account = get_account(current_user)
|
||||
if (account)
|
||||
account = get_account(current_user)
|
||||
if (account)
|
||||
begin
|
||||
account.destroy
|
||||
account.destroy
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
else
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to delete."
|
||||
end
|
||||
account
|
||||
end
|
||||
|
||||
|
||||
def get_account(current_user)
|
||||
current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
|
||||
rescue Recurly::Error => x
|
||||
|
|
@ -51,9 +51,9 @@ module JamRuby
|
|||
def update_account(current_user, billing_info=nil)
|
||||
account = get_account(current_user)
|
||||
if(account.present?)
|
||||
options = account_hash(current_user, billing_info)
|
||||
options = account_hash(current_user, billing_info)
|
||||
begin
|
||||
account.update_attributes(options)
|
||||
account.update_attributes(options)
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
|
|
@ -61,12 +61,20 @@ module JamRuby
|
|||
account
|
||||
end
|
||||
|
||||
def payment_history(current_user)
|
||||
def payment_history(current_user, options ={})
|
||||
|
||||
limit = params[:limit]
|
||||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
cursor = options[:cursor]
|
||||
|
||||
payments = []
|
||||
account = get_account(current_user)
|
||||
if(account.present?)
|
||||
begin
|
||||
account.transactions.find_each do |transaction|
|
||||
|
||||
account.transaction.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 << {
|
||||
|
|
@ -74,7 +82,8 @@ module JamRuby
|
|||
:amount_in_cents => transaction.amount_in_cents,
|
||||
:status => transaction.status,
|
||||
:payment_method => transaction.payment_method,
|
||||
:reference => transaction.reference
|
||||
:reference => transaction.reference,
|
||||
:plan_code => transaction.plan_code
|
||||
}
|
||||
#end
|
||||
end
|
||||
|
|
@ -95,7 +104,7 @@ module JamRuby
|
|||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
|
||||
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
||||
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to update billing info."
|
||||
end
|
||||
|
|
@ -121,21 +130,21 @@ module JamRuby
|
|||
#puts "subscription.plan.plan_code: #{subscription.plan.plan_code} / #{jam_track.plan_code} / #{subscription.plan.plan_code == jam_track.plan_code}"
|
||||
if(subscription.plan.plan_code == jam_track.plan_code)
|
||||
subscription.terminate(:full)
|
||||
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
|
||||
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
|
||||
terminated = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if terminated
|
||||
jam_track_right.destroy()
|
||||
jam_track_right.destroy()
|
||||
else
|
||||
raise RecurlyClientError, "Subscription '#{jam_track.plan_code}' not found for this user; could not issue refund."
|
||||
end
|
||||
|
||||
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to refund order."
|
||||
end
|
||||
|
|
@ -177,89 +186,18 @@ module JamRuby
|
|||
raise RecurlyClientError.new(plan.errors) if plan.errors.any?
|
||||
end
|
||||
|
||||
def place_order(current_user, jam_track, shopping_cart, sale)
|
||||
jam_track_right = nil
|
||||
account = get_account(current_user)
|
||||
if (account.present?)
|
||||
begin
|
||||
|
||||
# see if we can find existing plan for this plan_code, which should occur for previous-in-time error scenarios
|
||||
recurly_subscription_uuid = nil
|
||||
account.subscriptions.find_each do |subscription|
|
||||
if subscription.plan.plan_code == jam_track.plan_code
|
||||
recurly_subscription_uuid = subscription.uuid
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
free = false
|
||||
|
||||
# this means we already have a subscription, so don't try to create a new one for the same plan (Recurly would fail this anyway)
|
||||
unless recurly_subscription_uuid
|
||||
|
||||
# if the shopping cart was specified, see if the item should be free
|
||||
free = shopping_cart.nil? ? false : shopping_cart.free?
|
||||
# and if it's free, squish the charge to 0.
|
||||
unit_amount_in_cents = free ? 0 : nil
|
||||
subscription = Recurly::Subscription.create(:account=>account, :plan_code=>jam_track.plan_code, unit_amount_in_cents: unit_amount_in_cents)
|
||||
|
||||
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
|
||||
|
||||
# add a line item for the sale
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, subscription.uuid)
|
||||
|
||||
unless sale_line_item.valid?
|
||||
@log.error("sale item invalid! #{sale_line_item.errors.inspect}")
|
||||
puts("sale item invalid! #{sale_line_item.errors.inspect}")
|
||||
Stats.write('web.recurly.purchase.sale_invalid', {message: sale.errors.to_s, value:1})
|
||||
end
|
||||
|
||||
# delete from shopping cart the subscription
|
||||
shopping_cart.destroy if shopping_cart
|
||||
|
||||
recurly_subscription_uuid = subscription.uuid
|
||||
end
|
||||
|
||||
#raise RecurlyClientError, "Plan code '#{paid_subscription.plan_code}' doesn't match jam track: '#{jam_track.plan_code}'" unless recurly_subscription_uuid
|
||||
|
||||
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
|
||||
jam_track_right.redeemed = free
|
||||
end
|
||||
|
||||
# also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks
|
||||
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false) if free
|
||||
|
||||
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
|
||||
if jam_track_right.recurly_subscription_uuid != recurly_subscription_uuid
|
||||
jam_track_right.recurly_subscription_uuid = recurly_subscription_uuid
|
||||
jam_track_right.save
|
||||
end
|
||||
|
||||
raise RecurlyClientError.new("Error creating jam_track_right for jam_track: #{jam_track.id}") if jam_track_right.nil?
|
||||
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
|
||||
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to place order."
|
||||
end
|
||||
jam_track_right
|
||||
end
|
||||
|
||||
def find_or_create_account(current_user, billing_info)
|
||||
account = get_account(current_user)
|
||||
|
||||
|
||||
if(account.nil?)
|
||||
account = create_account(current_user, billing_info)
|
||||
else
|
||||
update_billing_info(current_user, billing_info)
|
||||
end
|
||||
account
|
||||
end
|
||||
account
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
def account_hash(current_user, billing_info)
|
||||
options = {
|
||||
|
|
@ -273,7 +211,7 @@ module JamRuby
|
|||
country: current_user.country
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
options[:billing_info] = billing_info if billing_info
|
||||
options
|
||||
end
|
||||
|
|
@ -282,11 +220,11 @@ module JamRuby
|
|||
class RecurlyClientError < Exception
|
||||
attr_accessor :errors
|
||||
def initialize(data)
|
||||
if data.respond_to?('has_key?')
|
||||
self.errors = data
|
||||
if data.respond_to?('has_key?')
|
||||
self.errors = data
|
||||
else
|
||||
self.errors = {:message=>data.to_s}
|
||||
end
|
||||
end
|
||||
end # initialize
|
||||
|
||||
def to_s
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ require 'spec_helper'
|
|||
|
||||
describe RecurlyTransactionWebHook do
|
||||
|
||||
|
||||
let(:refund_xml) {'<?xml version="1.0" encoding="UTF-8"?>
|
||||
<successful_refund_notification>
|
||||
<account>
|
||||
|
|
@ -120,8 +121,15 @@ describe RecurlyTransactionWebHook do
|
|||
|
||||
it "deletes jam_track_right when refunded" do
|
||||
|
||||
sale = Sale.create_jam_track_sale(@user)
|
||||
sale.recurly_invoice_id = '2da71ad9c657adf9fe618e4f058c78bb'
|
||||
sale.recurly_total_in_cents = 216
|
||||
sale.save!
|
||||
# create a jam_track right, which should be whacked as soon as we craete the web hook
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right, user: @user, recurly_subscription_uuid: '2da71ad97c826a7b784c264ac59c04de')
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right, user: @user, recurly_adjustment_uuid: 'bleh')
|
||||
|
||||
shopping_cart = ShoppingCart.create(@user, jam_track_right.jam_track)
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, '2da71ad9c657adf9fe618e4f058c78bb', nil)
|
||||
|
||||
document = Nokogiri::XML(refund_xml)
|
||||
|
||||
|
|
@ -131,8 +139,16 @@ describe RecurlyTransactionWebHook do
|
|||
end
|
||||
|
||||
it "deletes jam_track_right when voided" do
|
||||
|
||||
sale = Sale.create_jam_track_sale(@user)
|
||||
sale.recurly_invoice_id = '2da71ad9c657adf9fe618e4f058c78bb'
|
||||
sale.recurly_total_in_cents = 216
|
||||
sale.save!
|
||||
# create a jam_track right, which should be whacked as soon as we craete the web hook
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right, user: @user, recurly_subscription_uuid: '2da71ad97c826a7b784c264ac59c04de')
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right, user: @user, recurly_adjustment_uuid: 'blah')
|
||||
|
||||
shopping_cart = ShoppingCart.create(@user, jam_track_right.jam_track)
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, '2da71ad9c657adf9fe618e4f058c78bb', nil)
|
||||
|
||||
document = Nokogiri::XML(void_xml)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe SaleLineItem do
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:user2) {FactoryGirl.create(:user)}
|
||||
let(:jam_track) {FactoryGirl.create(:jam_track)}
|
||||
|
||||
describe "associations" do
|
||||
|
||||
it "can find associated recurly transaction web hook" do
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
|
||||
transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
|
||||
|
||||
sale_line_item.reload
|
||||
sale_line_item.recurly_transactions.should eq([transaction])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "state" do
|
||||
|
||||
it "success" do
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
|
||||
transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
|
||||
|
||||
sale_line_item.reload
|
||||
sale_line_item.state.should eq({
|
||||
void: false,
|
||||
refund: false,
|
||||
fail: false,
|
||||
success: true
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,72 +1,329 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Sale do
|
||||
|
||||
describe "check_integrity" do
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:user2) {FactoryGirl.create(:user)}
|
||||
let(:jam_track) {FactoryGirl.create(:jam_track)}
|
||||
|
||||
describe "index" do
|
||||
it "empty" do
|
||||
result = Sale.index(user)
|
||||
result[:query].length.should eq(0)
|
||||
result[:next].should eq(nil)
|
||||
end
|
||||
|
||||
it "one" do
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
|
||||
|
||||
result = Sale.index(user)
|
||||
result[:query].length.should eq(1)
|
||||
result[:next].should eq(nil)
|
||||
end
|
||||
|
||||
it "user filtered correctly" do
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
|
||||
|
||||
result = Sale.index(user)
|
||||
result[:query].length.should eq(1)
|
||||
result[:next].should eq(nil)
|
||||
|
||||
sale2 = Sale.create_jam_track_sale(user2)
|
||||
shopping_cart = ShoppingCart.create(user2, jam_track)
|
||||
sale_line_item2 = SaleLineItem.create_from_shopping_cart(sale2, shopping_cart, nil, 'some_adjustment_uuid', nil)
|
||||
|
||||
result = Sale.index(user)
|
||||
result[:query].length.should eq(1)
|
||||
result[:next].should eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "place_order" do
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:jam_track) {FactoryGirl.create(:jam_track)}
|
||||
let(:jamtrack) { FactoryGirl.create(:jam_track) }
|
||||
let(:jam_track_price_in_cents) { (jamtrack.price * 100).to_i }
|
||||
let(:client) { RecurlyClient.new }
|
||||
let(:billing_info) {
|
||||
info = {}
|
||||
info[:first_name] = user.first_name
|
||||
info[:last_name] = user.last_name
|
||||
info[:address1] = 'Test Address 1'
|
||||
info[:address2] = 'Test Address 2'
|
||||
info[:city] = user.city
|
||||
info[:state] = user.state
|
||||
info[:country] = user.country
|
||||
info[:zip] = '12345'
|
||||
info[:number] = '4111-1111-1111-1111'
|
||||
info[:month] = '08'
|
||||
info[:year] = '2017'
|
||||
info[:verification_value] = '111'
|
||||
info
|
||||
}
|
||||
|
||||
after(:each) do
|
||||
if user.recurly_code
|
||||
account = Recurly::Account.find(user.recurly_code)
|
||||
if account.present?
|
||||
account.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it "for a free jam track" do
|
||||
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
|
||||
client.find_or_create_account(user, billing_info)
|
||||
|
||||
sales = Sale.place_order(user, [shopping_cart])
|
||||
|
||||
user.reload
|
||||
user.sales.length.should eq(1)
|
||||
|
||||
sales.should eq(user.sales)
|
||||
sale = sales[0]
|
||||
sale.recurly_invoice_id.should_not be_nil
|
||||
|
||||
sale.recurly_subtotal_in_cents.should eq(jam_track_price_in_cents)
|
||||
sale.recurly_tax_in_cents.should eq(0)
|
||||
sale.recurly_total_in_cents.should eq(0)
|
||||
sale.recurly_currency.should eq('USD')
|
||||
sale.order_total.should eq(0)
|
||||
sale.sale_line_items.length.should == 1
|
||||
sale_line_item = sale.sale_line_items[0]
|
||||
sale_line_item.recurly_tax_in_cents.should eq(0)
|
||||
sale_line_item.recurly_total_in_cents.should eq(jam_track_price_in_cents)
|
||||
sale_line_item.recurly_currency.should eq('USD')
|
||||
sale_line_item.recurly_discount_in_cents.should eq(0)
|
||||
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
|
||||
sale_line_item.unit_price.should eq(jamtrack.price)
|
||||
sale_line_item.quantity.should eq(1)
|
||||
sale_line_item.free.should eq(1)
|
||||
sale_line_item.sales_tax.should be_nil
|
||||
sale_line_item.shipping_handling.should eq(0)
|
||||
sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code)
|
||||
sale_line_item.product_id.should eq(jamtrack.id)
|
||||
sale_line_item.recurly_subscription_uuid.should be_nil
|
||||
sale_line_item.recurly_adjustment_uuid.should_not be_nil
|
||||
sale_line_item.recurly_adjustment_credit_uuid.should_not be_nil
|
||||
sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid)
|
||||
sale_line_item.recurly_adjustment_credit_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_credit_uuid)
|
||||
|
||||
# verify subscription is in Recurly
|
||||
recurly_account = client.get_account(user)
|
||||
adjustments = recurly_account.adjustments
|
||||
adjustments.should_not be_nil
|
||||
adjustments.should have(2).items
|
||||
free_purchase= adjustments[0]
|
||||
free_purchase.unit_amount_in_cents.should eq((jamtrack.price * 100).to_i)
|
||||
free_purchase.accounting_code.should eq(ShoppingCart::PURCHASE_FREE)
|
||||
free_purchase.description.should eq("JamTrack: " + jamtrack.name)
|
||||
free_purchase.state.should eq('invoiced')
|
||||
free_purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
|
||||
|
||||
free_credit = adjustments[1]
|
||||
free_credit.unit_amount_in_cents.should eq(-(jamtrack.price * 100).to_i)
|
||||
free_credit.accounting_code.should eq(ShoppingCart::PURCHASE_FREE_CREDIT)
|
||||
free_credit.description.should eq("JamTrack: " + jamtrack.name + " (Credit)")
|
||||
free_credit.state.should eq('invoiced')
|
||||
free_credit.uuid.should eq(sale_line_item.recurly_adjustment_credit_uuid)
|
||||
|
||||
invoices = recurly_account.invoices
|
||||
invoices.should have(1).items
|
||||
invoice = invoices[0]
|
||||
invoice.uuid.should eq(sale.recurly_invoice_id)
|
||||
invoice.line_items.should have(2).items # should have both adjustments associated
|
||||
invoice.line_items[0].should eq(free_credit)
|
||||
invoice.line_items[1].should eq(free_purchase)
|
||||
invoice.subtotal_in_cents.should eq((jamtrack.price * 100).to_i)
|
||||
invoice.total_in_cents.should eq(0)
|
||||
invoice.state.should eq('collected')
|
||||
|
||||
# verify jam_track_rights data
|
||||
user.jam_track_rights.should_not be_nil
|
||||
user.jam_track_rights.should have(1).items
|
||||
user.jam_track_rights.last.jam_track.id.should eq(jamtrack.id)
|
||||
user.jam_track_rights.last.redeemed.should be_true
|
||||
user.has_redeemable_jamtrack.should be_false
|
||||
end
|
||||
|
||||
it "for a normally priced jam track" do
|
||||
user.has_redeemable_jamtrack = false
|
||||
user.save!
|
||||
shopping_cart = ShoppingCart.create user, jamtrack, 1, false
|
||||
client.find_or_create_account(user, billing_info)
|
||||
|
||||
sales = Sale.place_order(user, [shopping_cart])
|
||||
|
||||
user.reload
|
||||
user.sales.length.should eq(1)
|
||||
|
||||
sales.should eq(user.sales)
|
||||
sale = sales[0]
|
||||
sale.recurly_invoice_id.should_not be_nil
|
||||
|
||||
sale.recurly_subtotal_in_cents.should eq(jam_track_price_in_cents)
|
||||
sale.recurly_tax_in_cents.should eq(0)
|
||||
sale.recurly_total_in_cents.should eq(jam_track_price_in_cents)
|
||||
sale.recurly_currency.should eq('USD')
|
||||
|
||||
sale.order_total.should eq(jamtrack.price)
|
||||
sale.sale_line_items.length.should == 1
|
||||
sale_line_item = sale.sale_line_items[0]
|
||||
# validate we are storing pricing info from recurly
|
||||
sale_line_item.recurly_tax_in_cents.should eq(0)
|
||||
sale_line_item.recurly_total_in_cents.should eq(jam_track_price_in_cents)
|
||||
sale_line_item.recurly_currency.should eq('USD')
|
||||
sale_line_item.recurly_discount_in_cents.should eq(0)
|
||||
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
|
||||
sale_line_item.unit_price.should eq(jamtrack.price)
|
||||
sale_line_item.quantity.should eq(1)
|
||||
sale_line_item.free.should eq(0)
|
||||
sale_line_item.sales_tax.should be_nil
|
||||
sale_line_item.shipping_handling.should eq(0)
|
||||
sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code)
|
||||
sale_line_item.product_id.should eq(jamtrack.id)
|
||||
sale_line_item.recurly_subscription_uuid.should be_nil
|
||||
sale_line_item.recurly_adjustment_uuid.should_not be_nil
|
||||
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
|
||||
sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid)
|
||||
|
||||
# verify subscription is in Recurly
|
||||
recurly_account = client.get_account(user)
|
||||
adjustments = recurly_account.adjustments
|
||||
adjustments.should_not be_nil
|
||||
adjustments.should have(1).items
|
||||
purchase= adjustments[0]
|
||||
purchase.unit_amount_in_cents.should eq((jamtrack.price * 100).to_i)
|
||||
purchase.accounting_code.should eq(ShoppingCart::PURCHASE_NORMAL)
|
||||
purchase.description.should eq("JamTrack: " + jamtrack.name)
|
||||
purchase.state.should eq('invoiced')
|
||||
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
|
||||
|
||||
invoices = recurly_account.invoices
|
||||
invoices.should have(1).items
|
||||
invoice = invoices[0]
|
||||
invoice.uuid.should eq(sale.recurly_invoice_id)
|
||||
invoice.line_items.should have(1).items # should have single adjustment associated
|
||||
invoice.line_items[0].should eq(purchase)
|
||||
invoice.subtotal_in_cents.should eq((jamtrack.price * 100).to_i)
|
||||
invoice.total_in_cents.should eq((jamtrack.price * 100).to_i)
|
||||
invoice.state.should eq('collected')
|
||||
|
||||
# verify jam_track_rights data
|
||||
user.jam_track_rights.should_not be_nil
|
||||
user.jam_track_rights.should have(1).items
|
||||
user.jam_track_rights.last.jam_track.id.should eq(jamtrack.id)
|
||||
user.jam_track_rights.last.redeemed.should be_false
|
||||
user.has_redeemable_jamtrack.should be_false
|
||||
end
|
||||
|
||||
it "for a jamtrack already owned" do
|
||||
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
|
||||
client.find_or_create_account(user, billing_info)
|
||||
|
||||
sales = Sale.place_order(user, [shopping_cart])
|
||||
|
||||
user.reload
|
||||
user.sales.length.should eq(1)
|
||||
|
||||
shopping_cart = ShoppingCart.create user, jamtrack, 1, false
|
||||
sales = Sale.place_order(user, [shopping_cart])
|
||||
sales.should have(0).items
|
||||
# also, verify that no earlier adjustments were affected
|
||||
recurly_account = client.get_account(user)
|
||||
adjustments = recurly_account.adjustments
|
||||
adjustments.should have(2).items
|
||||
end
|
||||
|
||||
# this test counts on the fact that two adjustments are made when buying a free JamTrack
|
||||
# so if we make the second adjustment invalid from Recurly's standpoint, then
|
||||
# we can see if the first one is ultimately destroyed
|
||||
it "rolls back created adjustments if error" do
|
||||
|
||||
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
|
||||
|
||||
# grab the real response; we will modify it to make a nil accounting code
|
||||
adjustment_attrs = shopping_cart.create_adjustment_attributes(user)
|
||||
client.find_or_create_account(user, billing_info)
|
||||
|
||||
adjustment_attrs[1][:unit_amount_in_cents] = nil # invalid amount
|
||||
ShoppingCart.any_instance.stub(:create_adjustment_attributes).and_return(adjustment_attrs)
|
||||
|
||||
expect { Sale.place_order(user, [shopping_cart]) }.to raise_error(JamRuby::RecurlyClientError)
|
||||
|
||||
user.reload
|
||||
user.sales.should have(0).items
|
||||
|
||||
recurly_account = client.get_account(user)
|
||||
recurly_account.adjustments.should have(0).items
|
||||
end
|
||||
|
||||
it "rolls back adjustments created before the order" do
|
||||
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
|
||||
client.find_or_create_account(user, billing_info)
|
||||
|
||||
# create a single adjustment on the account
|
||||
adjustment_attrs = shopping_cart.create_adjustment_attributes(user)
|
||||
recurly_account = client.get_account(user)
|
||||
adjustment = recurly_account.adjustments.new (adjustment_attrs[0])
|
||||
adjustment.save
|
||||
adjustment.errors.any?.should be_false
|
||||
|
||||
sales = Sale.place_order(user, [shopping_cart])
|
||||
|
||||
user.reload
|
||||
|
||||
recurly_account = client.get_account(user)
|
||||
adjustments = recurly_account.adjustments
|
||||
adjustments.should have(2).items # two adjustments are created for a free jamtrack; that should be all there is
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_integrity_of_jam_track_sales" do
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:jam_track) { FactoryGirl.create(:jam_track) }
|
||||
|
||||
it "empty" do
|
||||
check_integrity = Sale.check_integrity
|
||||
check_integrity = Sale.check_integrity_of_jam_track_sales
|
||||
check_integrity.length.should eq(1)
|
||||
r = check_integrity[0]
|
||||
r.total.to_i.should eq(0)
|
||||
r.not_known.to_i.should eq(0)
|
||||
r.succeeded.to_i.should eq(0)
|
||||
r.failed.to_i.should eq(0)
|
||||
r.refunded.to_i.should eq(0)
|
||||
r.voided.to_i.should eq(0)
|
||||
end
|
||||
|
||||
it "one unknown sale" do
|
||||
sale = Sale.create(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid')
|
||||
|
||||
check_integrity = Sale.check_integrity
|
||||
r = check_integrity[0]
|
||||
r.total.to_i.should eq(1)
|
||||
r.not_known.to_i.should eq(1)
|
||||
r.succeeded.to_i.should eq(0)
|
||||
r.failed.to_i.should eq(0)
|
||||
r.refunded.to_i.should eq(0)
|
||||
r.voided.to_i.should eq(0)
|
||||
end
|
||||
|
||||
it "one succeeded sale" do
|
||||
sale = Sale.create(user)
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid')
|
||||
FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_recurly_invoice_id', nil)
|
||||
|
||||
|
||||
check_integrity = Sale.check_integrity
|
||||
check_integrity = Sale.check_integrity_of_jam_track_sales
|
||||
r = check_integrity[0]
|
||||
r.total.to_i.should eq(1)
|
||||
r.not_known.to_i.should eq(0)
|
||||
r.succeeded.to_i.should eq(1)
|
||||
r.failed.to_i.should eq(0)
|
||||
r.refunded.to_i.should eq(0)
|
||||
r.voided.to_i.should eq(0)
|
||||
end
|
||||
|
||||
it "one failed sale" do
|
||||
sale = Sale.create(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid')
|
||||
FactoryGirl.create(:recurly_transaction_web_hook_failed, subscription_id: 'some_recurly_uuid')
|
||||
|
||||
check_integrity = Sale.check_integrity
|
||||
it "one voided sale" do
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
sale.recurly_invoice_id = 'some_recurly_invoice_id'
|
||||
sale.save!
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_recurly_invoice_id', nil)
|
||||
FactoryGirl.create(:recurly_transaction_web_hook, transaction_type: RecurlyTransactionWebHook::VOID, invoice_id: 'some_recurly_invoice_id')
|
||||
|
||||
check_integrity = Sale.check_integrity_of_jam_track_sales
|
||||
r = check_integrity[0]
|
||||
r.total.to_i.should eq(1)
|
||||
r.not_known.to_i.should eq(0)
|
||||
r.succeeded.to_i.should eq(0)
|
||||
r.failed.to_i.should eq(1)
|
||||
r.refunded.to_i.should eq(0)
|
||||
r.voided.to_i.should eq(0)
|
||||
r.voided.to_i.should eq(1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,10 @@ describe ShoppingCart do
|
|||
it "should not add duplicate JamTrack to ShoppingCart" do
|
||||
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
|
||||
cart1.should_not be_nil
|
||||
cart1.errors.any?.should be_false
|
||||
user.reload
|
||||
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
|
||||
cart2.should be_nil
|
||||
cart2.errors.any?.should be_true
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -86,42 +86,7 @@ describe RecurlyClient do
|
|||
found.state.should eq('closed')
|
||||
end
|
||||
|
||||
it "can place order" do
|
||||
sale = Sale.create(@user)
|
||||
sale = Sale.find(sale.id)
|
||||
shopping_cart = ShoppingCart.create @user, @jamtrack, 1, true
|
||||
history_items = @client.payment_history(@user).length
|
||||
@client.find_or_create_account(@user, @billing_info)
|
||||
expect{@client.place_order(@user, @jamtrack, shopping_cart, sale)}.not_to raise_error()
|
||||
|
||||
# verify jam_track_rights data
|
||||
@user.jam_track_rights.should_not be_nil
|
||||
@user.jam_track_rights.should have(1).items
|
||||
@user.jam_track_rights.last.jam_track.id.should eq(@jamtrack.id)
|
||||
|
||||
# verify sales data
|
||||
sale = Sale.find(sale.id)
|
||||
sale.sale_line_items.length.should == 1
|
||||
sale_line_item = sale.sale_line_items[0]
|
||||
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
|
||||
sale_line_item.unit_price.should eq(@jamtrack.price)
|
||||
sale_line_item.quantity.should eq(1)
|
||||
sale_line_item.free.should eq(1)
|
||||
sale_line_item.sales_tax.should be_nil
|
||||
sale_line_item.shipping_handling.should eq(0)
|
||||
sale_line_item.recurly_plan_code.should eq(@jamtrack.plan_code)
|
||||
sale_line_item.product_id.should eq(@jamtrack.id)
|
||||
sale_line_item.recurly_subscription_uuid.should_not be_nil
|
||||
sale_line_item.recurly_subscription_uuid.should eq(@user.jam_track_rights.last.recurly_subscription_uuid)
|
||||
|
||||
# verify subscription is in Recurly
|
||||
subs = @client.get_account(@user).subscriptions
|
||||
subs.should_not be_nil
|
||||
subs.should have(1).items
|
||||
|
||||
@client.payment_history(@user).should have(history_items+1).items
|
||||
end
|
||||
|
||||
=begin
|
||||
it "can refund subscription" do
|
||||
sale = Sale.create(@user)
|
||||
shopping_cart = ShoppingCart.create @user, @jamtrack, 1
|
||||
|
|
@ -141,18 +106,7 @@ describe RecurlyClient do
|
|||
@jamtrack.reload
|
||||
@jamtrack.jam_track_rights.should have(0).items
|
||||
end
|
||||
=end
|
||||
|
||||
it "detects error on double order" do
|
||||
sale = Sale.create(@user)
|
||||
shopping_cart = ShoppingCart.create @user, @jamtrack, 1
|
||||
@client.find_or_create_account(@user, @billing_info)
|
||||
jam_track_right = @client.place_order(@user, @jamtrack, shopping_cart, sale)
|
||||
jam_track_right.recurly_subscription_uuid.should_not be_nil
|
||||
|
||||
shopping_cart = ShoppingCart.create @user, @jamtrack, 1
|
||||
jam_track_right2 = @client.place_order(@user, @jamtrack, shopping_cart, sale)
|
||||
jam_track_right.should eq(jam_track_right2)
|
||||
jam_track_right.recurly_subscription_uuid.should eq(jam_track_right.recurly_subscription_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ describe UserMailer do
|
|||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
stub_const("APP_CONFIG", app_config)
|
||||
UserMailer.deliveries.clear
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' # cuz i'm not comfortable using aws
|
|||
def app_config
|
||||
klass = Class.new do
|
||||
|
||||
def email_alerts_alias
|
||||
'alerts@jamkazam.com'
|
||||
end
|
||||
|
||||
def email_generic_from
|
||||
'nobody@jamkazam.com'
|
||||
end
|
||||
|
||||
def aws_bucket
|
||||
JAMKAZAM_TESTING_BUCKET
|
||||
end
|
||||
|
|
@ -170,6 +178,10 @@ def app_config
|
|||
true
|
||||
end
|
||||
|
||||
def secret_token
|
||||
'foobar'
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
|
@ -240,4 +252,4 @@ end
|
|||
def friend(user1, user2)
|
||||
FactoryGirl.create(:friendship, user: user1, friend: user2)
|
||||
FactoryGirl.create(:friendship, user: user2, friend: user1)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
|
@ -55,7 +55,8 @@
|
|||
validProfiles : validProfiles,
|
||||
invalidProfiles : invalidProfiles,
|
||||
isNativeClient: gon.isNativeClient,
|
||||
musician: context.JK.currentUserMusician
|
||||
musician: context.JK.currentUserMusician,
|
||||
sales_count: userDetail.sales_count
|
||||
} , { variable: 'data' }));
|
||||
|
||||
$('#account-content-scroller').html($template);
|
||||
|
|
@ -113,7 +114,7 @@
|
|||
|
||||
// License dialog:
|
||||
$("#account-content-scroller").on('click', '#account-view-license-link', function(evt) {evt.stopPropagation(); app.layout.showDialog('jamtrack-license-dialog'); return false; } );
|
||||
$("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); app.layout.showDialog('jamtrack-payment-history-dialog'); return false; } );
|
||||
$("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); navToPaymentHistory(); return false; } );
|
||||
}
|
||||
|
||||
function renderAccount() {
|
||||
|
|
@ -157,6 +158,10 @@
|
|||
window.location = "/client#/account/audio"
|
||||
}
|
||||
|
||||
function navToPaymentHistory() {
|
||||
window.location = '/client#/account/paymentHistory'
|
||||
}
|
||||
|
||||
// handle update avatar event
|
||||
function updateAvatar(avatar_url) {
|
||||
var photoUrl = context.JK.resolveAvatarUrl(avatar_url);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
context.JK ||= {}
|
||||
|
||||
context.JK.AccountPaymentHistoryScreen = class AccountPaymentHistoryScreen
|
||||
LIMIT = 20
|
||||
|
||||
constructor: (@app) ->
|
||||
@logger = context.JK.logger
|
||||
@rest = context.JK.Rest()
|
||||
@screen = null
|
||||
@scroller = null
|
||||
@genre = null
|
||||
@artist = null
|
||||
@instrument = null
|
||||
@availability = null
|
||||
@nextPager = null
|
||||
@noMoreSales = null
|
||||
@currentPage = 0
|
||||
@next = null
|
||||
@tbody = null
|
||||
@rowTemplate = null
|
||||
|
||||
beforeShow:(data) =>
|
||||
|
||||
|
||||
afterShow:(data) =>
|
||||
@refresh()
|
||||
|
||||
events:() =>
|
||||
@backBtn.on('click', @onBack)
|
||||
|
||||
onBack:() =>
|
||||
window.location = '/client#/account'
|
||||
return false
|
||||
|
||||
clearResults:() =>
|
||||
@currentPage = 0
|
||||
@tbody.empty()
|
||||
@noMoreSales.hide()
|
||||
@next = null
|
||||
|
||||
|
||||
|
||||
refresh:() =>
|
||||
@currentQuery = this.buildQuery()
|
||||
@rest.getSalesHistory(@currentQuery)
|
||||
.done(@salesHistoryDone)
|
||||
.fail(@salesHistoryFail)
|
||||
|
||||
|
||||
renderPayments:(response) =>
|
||||
if response.entries? && response.entries.length > 0
|
||||
for sale in response.entries
|
||||
amt = sale.recurly_total_in_cents
|
||||
amt = 0 if !amt?
|
||||
|
||||
original_total = sale.state.original_total
|
||||
refund_total = sale.state.refund_total
|
||||
|
||||
refund_state = null
|
||||
if original_total != 0 # the enclosed logic does not work for free purchases
|
||||
if refund_total == original_total
|
||||
refund_state = 'refunded'
|
||||
else if refund_total != 0 and refund_total < original_total
|
||||
refund_state = 'partial refund'
|
||||
|
||||
|
||||
displayAmount = (amt/100).toFixed(2)
|
||||
status = 'paid'
|
||||
|
||||
if sale.state.voided
|
||||
status = 'voided'
|
||||
displayAmount = (0).toFixed(2)
|
||||
else if refund_state?
|
||||
status = refund_state
|
||||
displayAmount = (amt/100).toFixed(2) + " (refunded: #{(refund_total/100).toFixed(2)})"
|
||||
|
||||
description = []
|
||||
for line_item in sale.line_items
|
||||
description.push(line_item.product_info?.name)
|
||||
|
||||
payment = {
|
||||
date: context.JK.formatDate(sale.created_at, true)
|
||||
amount: displayAmount
|
||||
status: status
|
||||
payment_method: 'Credit Card',
|
||||
description: description.join(', ')
|
||||
}
|
||||
|
||||
tr = $(context._.template(@rowTemplate, payment, { variable: 'data' }));
|
||||
@tbody.append(tr);
|
||||
else
|
||||
tr = "<tr><td class='center' colspan='5'>No payments found</td></tr>"
|
||||
@tbody.append(tr);
|
||||
|
||||
salesHistoryDone:(response) =>
|
||||
|
||||
# Turn in to HTML rows and append:
|
||||
#@tbody.html("")
|
||||
console.log("response.next", response)
|
||||
@next = response.next_page
|
||||
@renderPayments(response)
|
||||
if response.next_page == null
|
||||
# if we less results than asked for, end searching
|
||||
@scroller.infinitescroll 'pause'
|
||||
@logger.debug("end of history")
|
||||
if @currentPage > 0
|
||||
@noMoreSales.show()
|
||||
# there are bugs with infinitescroll not removing the 'loading'.
|
||||
# it's most noticeable at the end of the list, so whack all such entries
|
||||
$('.infinite-scroll-loader').remove()
|
||||
else
|
||||
@currentPage++
|
||||
this.buildQuery()
|
||||
this.registerInfiniteScroll()
|
||||
|
||||
|
||||
salesHistoryFail:(jqXHR)=>
|
||||
@noMoreSales.show()
|
||||
@app.notifyServerError jqXHR, 'Payment History Unavailable'
|
||||
|
||||
defaultQuery:() =>
|
||||
query =
|
||||
per_page: LIMIT
|
||||
page: @currentPage+1
|
||||
if @next
|
||||
query.since = @next
|
||||
query
|
||||
|
||||
buildQuery:() =>
|
||||
@currentQuery = this.defaultQuery()
|
||||
|
||||
|
||||
registerInfiniteScroll:() =>
|
||||
that = this
|
||||
@scroller.infinitescroll {
|
||||
behavior: 'local'
|
||||
navSelector: '#account-payment-history .btn-next-pager'
|
||||
nextSelector: '#account-payment-history .btn-next-pager'
|
||||
binder: @scroller
|
||||
dataType: 'json'
|
||||
appendCallback: false
|
||||
prefill: false
|
||||
bufferPx: 100
|
||||
loading:
|
||||
msg: $('<div class="infinite-scroll-loader">Loading ...</div>')
|
||||
img: '/assets/shared/spinner.gif'
|
||||
path: (page) =>
|
||||
'/api/sales?' + $.param(that.buildQuery())
|
||||
|
||||
}, (json, opts) =>
|
||||
this.salesHistoryDone(json)
|
||||
@scroller.infinitescroll 'resume'
|
||||
|
||||
initialize:() =>
|
||||
screenBindings =
|
||||
'beforeShow': this.beforeShow
|
||||
'afterShow': this.afterShow
|
||||
@app.bindScreen 'account/paymentHistory', screenBindings
|
||||
@screen = $('#account-payment-history')
|
||||
@scroller = @screen.find('.content-body-scroller')
|
||||
@nextPager = @screen.find('a.btn-next-pager')
|
||||
@noMoreSales = @screen.find('.end-of-payments-list')
|
||||
@tbody = @screen.find("table.payment-table tbody")
|
||||
@rowTemplate = $('#template-payment-history-row').html()
|
||||
@backBtn = @screen.find('.back')
|
||||
|
||||
if @screen.length == 0
|
||||
throw new Error('@screen must be specified')
|
||||
if @scroller.length == 0
|
||||
throw new Error('@scroller must be specified')
|
||||
if @tbody.length == 0
|
||||
throw new Error('@tbody must be specified')
|
||||
if @noMoreSales.length == 0
|
||||
throw new Error('@noMoreSales must be specified')
|
||||
|
||||
this.events()
|
||||
|
||||
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
var sub_total = 0.0
|
||||
var taxes = 0.0
|
||||
$.each(carts, function(index, cart) {
|
||||
sub_total += parseFloat(cart.product_info.total_price)
|
||||
sub_total += parseFloat(cart.product_info.real_price)
|
||||
});
|
||||
if(carts.length == 0) {
|
||||
data.grand_total = '-.--'
|
||||
|
|
@ -147,68 +147,45 @@
|
|||
|
||||
var planPricing = {}
|
||||
|
||||
|
||||
var priceElement = $screen.find('.order-right-page .plan.jamtrack')
|
||||
|
||||
if(priceElement.length == 0) {
|
||||
logger.error("unable to find price element for jamtrack");
|
||||
app.notify({title: "Error Encountered", text: "Unable to find plan info for jam track"})
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("creating recurly pricing element for plan: " + gon.recurly_tax_estimate_jam_track_plan)
|
||||
|
||||
var effectiveQuantity = 0
|
||||
|
||||
context._.each(carts, function(cart) {
|
||||
var priceElement = $screen.find('.order-right-page .plan[data-plan-code="' + cart.product_info.plan_code +'"]')
|
||||
|
||||
if(priceElement.length == 0) {
|
||||
logger.error("unable to find price element for " + cart.product_info.plan_code, cart);
|
||||
app.notify({title: "Error Encountered", text: "Unable to find plan info for " + cart.product_info.plan_code})
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("creating recurly pricing element for plan: " + cart.product_info.plan_code)
|
||||
var pricing = context.recurly.Pricing();
|
||||
pricing.plan_code = cart.product_info.plan_code;
|
||||
pricing.resolved = false;
|
||||
pricing.effective_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
|
||||
planPricing[pricing.plan_code] = pricing;
|
||||
|
||||
// this is called when the plan is resolved against Recurly. It will have tax info, which is the only way we can get it.
|
||||
pricing.on('change', function(price) {
|
||||
|
||||
var resolvedPrice = planPricing[this.plan_code];
|
||||
if(!resolvedPrice) {
|
||||
logger.error("unable to find price info in storage")
|
||||
app.notify({title: "Error Encountered", text: "Unable to find plan info in storage"})
|
||||
return;
|
||||
}
|
||||
else {
|
||||
logger.debug("pricing resolved for plan: " + this.plan_code)
|
||||
}
|
||||
resolvedPrice.resolved = true;
|
||||
|
||||
var allResolved = true;
|
||||
var totalTax = 0;
|
||||
var totalPrice = 0;
|
||||
|
||||
// let's see if all plans have been resolved via API; and add up total price and taxes for display
|
||||
$.each(planPricing, function(plan_code, priceObject) {
|
||||
logger.debug("resolved recurly priceObject", priceObject)
|
||||
|
||||
if(!priceObject.resolved) {
|
||||
allResolved = false;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var unitTax = Number(priceObject.price.now.tax) * priceObject.effective_quantity;
|
||||
totalTax += unitTax;
|
||||
|
||||
var totalUnitPrice = Number(priceObject.price.now.total) * priceObject.effective_quantity;
|
||||
totalPrice += totalUnitPrice;
|
||||
}
|
||||
})
|
||||
|
||||
if(allResolved) {
|
||||
$screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2))
|
||||
$screen.find('.order-right-page .order-items-value.grand-total').text('$' + totalPrice.toFixed(2))
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("still waiting on more plans to resolve")
|
||||
}
|
||||
})
|
||||
pricing.attach(priceElement.eq(0))
|
||||
effectiveQuantity += cart.product_info.quantity - cart.product_info.marked_for_redeem
|
||||
})
|
||||
|
||||
var pricing = context.recurly.Pricing();
|
||||
pricing.plan_code = gon.recurly_tax_estimate_jam_track_plan;
|
||||
pricing.resolved = false;
|
||||
pricing.effective_quantity = 1
|
||||
|
||||
// this is called when the plan is resolved against Recurly. It will have tax info, which is the only way we can get it.
|
||||
pricing.on('change', function(price) {
|
||||
|
||||
var totalTax = 0;
|
||||
var totalPrice = 0;
|
||||
|
||||
var unitTax = Number(pricing.price.now.tax) * effectiveQuantity;
|
||||
totalTax += unitTax;
|
||||
|
||||
var totalUnitPrice = Number(pricing.price.now.total) * effectiveQuantity;
|
||||
totalPrice += totalUnitPrice;
|
||||
|
||||
$screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2))
|
||||
$screen.find('.order-right-page .order-items-value.grand-total').text('$' + totalPrice.toFixed(2))
|
||||
})
|
||||
|
||||
pricing.attach(priceElement.eq(0))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# one time init stuff for the /client view
|
||||
|
||||
|
||||
$ = jQuery
|
||||
context = window
|
||||
context.JK ||= {};
|
||||
|
||||
context.JK.ClientInit = class ClientInit
|
||||
constructor: () ->
|
||||
@logger = context.JK.logger
|
||||
@gearUtils = context.JK.GearUtils
|
||||
|
||||
init: () =>
|
||||
if context.gon.isNativeClient
|
||||
this.nativeClientInit()
|
||||
|
||||
nativeClientInit: () =>
|
||||
@gearUtils.bootstrapDefaultPlaybackProfile();
|
||||
|
|
@ -9,6 +9,9 @@
|
|||
var $dialog = null;
|
||||
var $dontShowAgain = null;
|
||||
var $setupGearBtn = null;
|
||||
var $browserJamTrackBtn = null;
|
||||
var $jamTrackSection = null;
|
||||
var $jamTracksLimitedTime = null;
|
||||
|
||||
function handleStartAudioQualification() {
|
||||
|
||||
|
|
@ -45,6 +48,12 @@
|
|||
return false;
|
||||
})
|
||||
|
||||
$browserJamTrackBtn.click(function() {
|
||||
app.layout.closeDialog('getting-started')
|
||||
window.location = '/client#/jamtrack'
|
||||
return false;
|
||||
})
|
||||
|
||||
$('#getting-started-dialog a.facebook-invite').on('click', function (e) {
|
||||
invitationDialog.showFacebookDialog(e);
|
||||
});
|
||||
|
|
@ -59,13 +68,21 @@
|
|||
}
|
||||
|
||||
function beforeShow() {
|
||||
app.user().done(function(user) {
|
||||
var jamtrackRule = user.free_jamtrack ? 'has-free-jamtrack' : 'no-free-jamtrack'
|
||||
$jamTrackSection.removeClass('has-free-jamtrack').removeClass('no-free-jamtrack').addClass(jamtrackRule)
|
||||
if(user.free_jamtrack) {
|
||||
$jamTracksLimitedTime.removeClass('hidden')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function beforeHide() {
|
||||
var showWhatsNext = !$dontShowAgain.is(':checked')
|
||||
app.user().done(function(user) {
|
||||
app.updateUserModel({show_whats_next: showWhatsNext, show_whats_next_count: user.show_whats_next_count + 1})
|
||||
})
|
||||
|
||||
if ($dontShowAgain.is(':checked')) {
|
||||
app.updateUserModel({show_whats_next: false})
|
||||
}
|
||||
}
|
||||
|
||||
function initializeButtons() {
|
||||
|
|
@ -84,6 +101,9 @@
|
|||
$dialog = $('#getting-started-dialog');
|
||||
$dontShowAgain = $dialog.find('#show_getting_started');
|
||||
$setupGearBtn = $dialog.find('.setup-gear-btn')
|
||||
$browserJamTrackBtn = $dialog.find('.browse-jamtrack');
|
||||
$jamTrackSection = $dialog.find('.get-a-free-jamtrack-section')
|
||||
$jamTracksLimitedTime = $dialog.find('.jamtracks-limited-time')
|
||||
|
||||
registerEvents();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
context.JK = context.JK || {};
|
||||
context.JK.SessionSettingsDialog = function(app, sessionScreen) {
|
||||
var logger = context.JK.logger;
|
||||
var gearUtils = context.JK.GearUtilsInstance;
|
||||
var $dialog;
|
||||
var $screen = $('#session-settings');
|
||||
var $selectedFilenames = $screen.find('#selected-filenames');
|
||||
|
|
@ -15,6 +16,8 @@
|
|||
|
||||
function beforeShow(data) {
|
||||
|
||||
var canPlayWithOthers = gearUtils.canPlayWithOthers();
|
||||
|
||||
context.JK.GenreSelectorHelper.render('#session-settings-genre');
|
||||
$dialog = $('[layout-id="session-settings"]');
|
||||
|
||||
|
|
@ -72,6 +75,10 @@
|
|||
context.JK.dropdown($('#session-settings-language'));
|
||||
context.JK.dropdown($('#session-settings-musician-access'));
|
||||
context.JK.dropdown($('#session-settings-fan-access'));
|
||||
|
||||
var easyDropDownState = canPlayWithOthers.canPlay ? 'enable' : 'disable'
|
||||
$('#session-settings-musician-access').easyDropDown(easyDropDownState)
|
||||
$('#session-settings-fan-access').easyDropDown(easyDropDownState)
|
||||
}
|
||||
|
||||
function saveSettings(evt) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
context.JK ||= {}
|
||||
|
||||
context.JK.SinglePlayerProfileGuardDialog = class SinglePlayerProfileGuardDialog
|
||||
constructor: (@app) ->
|
||||
@rest = context.JK.Rest()
|
||||
@client = context.jamClient
|
||||
@logger = context.JK.logger
|
||||
@gearUtils = context.JK.GearUtilsInstance
|
||||
@screen = null
|
||||
@dialogId = 'single-player-profile-dialog';
|
||||
@dialog = null;
|
||||
|
||||
initialize:() =>
|
||||
dialogBindings = {
|
||||
'beforeShow' : @beforeShow,
|
||||
'afterShow' : @afterShow
|
||||
}
|
||||
|
||||
@dialog = $('[layout-id="' + @dialogId + '"]');
|
||||
@app.bindDialog(@dialogId, dialogBindings);
|
||||
@content = @dialog.find(".dialog-inner")
|
||||
@audioLatency = @dialog.find('.audio-latency')
|
||||
@btnPrivateSession = @dialog.find('.btn-private-session')
|
||||
@btnGearSetup = @dialog.find('.btn-gear-setup')
|
||||
|
||||
@btnPrivateSession.on('click', @onPrivateSessionChoice)
|
||||
@btnGearSetup.on('click', @onGearSetupChoice)
|
||||
|
||||
beforeShow:() =>
|
||||
@dialog.data('result', { choice: null})
|
||||
|
||||
|
||||
afterShow:() =>
|
||||
canPlayWithOthers = @gearUtils.canPlayWithOthers()
|
||||
|
||||
if canPlayWithOthers.isNoInputProfile
|
||||
@content.removeClass('high-latency').addClass('has-no-inputs')
|
||||
else
|
||||
@content.removeClass('has-no-input').addClass('high-latency')
|
||||
|
||||
latency = '?'
|
||||
if canPlayWithOthers.audioLatency?
|
||||
latency = canPlayWithOthers.audioLatency
|
||||
|
||||
@audioLatency.text("#{latency} milliseconds.")
|
||||
|
||||
onPrivateSessionChoice: () =>
|
||||
@dialog.data('result', { choice: 'private_session'})
|
||||
@app.layout.closeDialog(@dialogId)
|
||||
return false
|
||||
|
||||
onGearSetupChoice: () =>
|
||||
@dialog.data('result', { choice: 'gear_setup'})
|
||||
@app.layout.closeDialog(@dialogId)
|
||||
return false
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
if (!context.jamClient || !context.jamClient.IsNativeClient()) {
|
||||
|
||||
$('#video-dialog-header').html($self.data('video-header') || $self.attr('data-video-header'));
|
||||
$('#video-dialog-iframe').attr('src', $self.data('video-url') || $self.atr('data-video-url'));
|
||||
$('#video-dialog-iframe').attr('src', $self.data('video-url') || $self.attr('data-video-url'));
|
||||
app.layout.showDialog('video-dialog');
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
function events() {
|
||||
$('.carousel .slides').on('click', '.slideItem', videoClick);
|
||||
$('.video-slide').on('click', videoClick);
|
||||
$('.video-item').on('click', videoClick);
|
||||
|
||||
$(dialogId + '-close').click(function (e) {
|
||||
app.layout.closeDialog('video-dialog');
|
||||
|
|
|
|||
|
|
@ -204,8 +204,7 @@
|
|||
var user = app.user()
|
||||
if(user) {
|
||||
user.done(function(userProfile) {
|
||||
console.log("app.layout.getCurrentScreen() != 'checkoutOrderScreen'", app.layout.getCurrentScreen())
|
||||
if (userProfile.show_whats_next &&
|
||||
if (userProfile.show_whats_next && userProfile.show_whats_next_count < 10 &&
|
||||
window.location.pathname.indexOf(gon.client_path) == 0 &&
|
||||
window.location.pathname.indexOf('/checkout') == -1 &&
|
||||
!app.layout.isDialogShowing('getting-started'))
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@
|
|||
var metronomeBPM=false;
|
||||
var metronomeSound=false;
|
||||
var metronomeMeter=0;
|
||||
var backingTrackPath="";
|
||||
var backingTrackLoop=false;
|
||||
var backingTrackPath = "";
|
||||
var backingTrackLoop = false;
|
||||
var simulateNoInputs = false;
|
||||
|
||||
function dbg(msg) { logger.debug('FakeJamClient: ' + msg); }
|
||||
|
||||
|
|
@ -47,7 +48,13 @@
|
|||
function FTUEPageLeave() {}
|
||||
function FTUECancel() {}
|
||||
function FTUEGetMusicProfileName() {
|
||||
return "FTUEAttempt-1"
|
||||
|
||||
if(simulateNoInputs) {
|
||||
return "System Default (Playback Only)"
|
||||
}
|
||||
else {
|
||||
return "FTUEAttempt-1"
|
||||
}
|
||||
}
|
||||
function FTUESetMusicProfileName() {
|
||||
|
||||
|
|
@ -266,6 +273,10 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
function FTUECreateUpdatePlayBackProfile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function RegisterVolChangeCallBack(functionName) {
|
||||
dbg('RegisterVolChangeCallBack');
|
||||
}
|
||||
|
|
@ -444,6 +455,10 @@
|
|||
];
|
||||
var response = [];
|
||||
for (var i=0; i<mixerIds.length; i++) {
|
||||
|
||||
// for testing no inputs, set simulateNoInputs = true
|
||||
if(simulateNoInputs && i == 2) continue;
|
||||
|
||||
response.push({
|
||||
client_id: clientIds[i],
|
||||
group_id: groups[i],
|
||||
|
|
@ -1012,6 +1027,7 @@
|
|||
this.FTUELoadAudioConfiguration = FTUELoadAudioConfiguration;
|
||||
this.FTUEClearChannelAssignments = FTUEClearChannelAssignments;
|
||||
this.FTUEClearChatInput = FTUEClearChatInput;
|
||||
this.FTUECreateUpdatePlayBackProfile = FTUECreateUpdatePlayBackProfile;
|
||||
|
||||
// Session
|
||||
this.SessionAddTrack = SessionAddTrack;
|
||||
|
|
|
|||
|
|
@ -1499,9 +1499,18 @@
|
|||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getBackingTracks(options) {
|
||||
function getSalesHistory(options) {
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: '/api/sales?' + $.param(options),
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function getBackingTracks(options) {
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: '/api/backing_tracks?' + $.param(options),
|
||||
|
|
@ -1765,6 +1774,7 @@
|
|||
this.getJamtracks = getJamtracks;
|
||||
this.getPurchasedJamTracks = getPurchasedJamTracks;
|
||||
this.getPaymentHistory = getPaymentHistory;
|
||||
this.getSalesHistory = getSalesHistory;
|
||||
this.getJamTrackRight = getJamTrackRight;
|
||||
this.enqueueJamTrack = enqueueJamTrack;
|
||||
this.getBackingTracks = getBackingTracks;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ context.JK.JamTrackScreen=class JamTrackScreen
|
|||
for v in raw_vars
|
||||
[key, val] = v.split("=")
|
||||
params[key] = decodeURIComponent(val)
|
||||
ms
|
||||
params
|
||||
|
||||
refresh:() =>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
var $sliderBar = $('.recording-playback', $parentElement);
|
||||
var $slider = $('.recording-slider', $parentElement);
|
||||
var $playmodeButton = $('.playback-mode-buttons.icheckbuttons input', $parentElement);
|
||||
var $jamTrackGetReady = $('.jam-track-get-ready', $parentElement);
|
||||
|
||||
var $self = $(this);
|
||||
|
||||
|
|
@ -158,7 +159,9 @@
|
|||
setPlaybackMode(playmode);
|
||||
});
|
||||
|
||||
function styleControls( ) {
|
||||
function styleControls() {
|
||||
$jamTrackGetReady.attr('data-mode', playbackMonitorMode);
|
||||
|
||||
$parentElement.removeClass('mediafile-mode jamtrack-mode metronome-mode');
|
||||
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.MEDIA_FILE) {
|
||||
$parentElement.addClass('mediafile-mode');
|
||||
|
|
@ -194,6 +197,18 @@
|
|||
positionMs = 0;
|
||||
}
|
||||
|
||||
if(playbackMonitorMode = PLAYBACK_MONITOR_MODE.JAMTRACK) {
|
||||
|
||||
if(isPlaying) {
|
||||
$jamTrackGetReady.attr('data-current-time', positionMs)
|
||||
}
|
||||
else {
|
||||
// this is so the jamtrack 'Get Ready!' stays hidden when it's not playing
|
||||
$jamTrackGetReady.attr('data-current-time', -1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) {
|
||||
updateIsPlaying(isPlaying);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
context.JK = context.JK || {};
|
||||
|
||||
context.JK.CreateScheduledSession = function(app) {
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
var gearUtils = context.JK.GearUtilsInstance;
|
||||
var sessionUtils = context.JK.SessionUtils;
|
||||
var logger = context.JK.logger;
|
||||
var rest = JK.Rest();
|
||||
|
|
@ -597,7 +597,9 @@
|
|||
|
||||
if(willOptionStartSession()) {
|
||||
|
||||
gearUtils.guardAgainstInvalidConfiguration(app)
|
||||
var shouldVerifyNetwork = createSessionSettings.musician_access.value != 'only-rsvp';
|
||||
|
||||
gearUtils.guardAgainstInvalidConfiguration(app, shouldVerifyNetwork)
|
||||
.fail(function() {
|
||||
$btn.removeClass('disabled')
|
||||
app.notify(
|
||||
|
|
@ -908,6 +910,13 @@
|
|||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_QUICK_START %>';
|
||||
}
|
||||
|
||||
function optionRequiresMultiplayerProfile() {
|
||||
return createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_START_SCHEDULED%>' ||
|
||||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_IMMEDIATE %>' ||
|
||||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_RSVP %>' ||
|
||||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_SCHEDULE_FUTURE %>';
|
||||
}
|
||||
|
||||
function next(event) {
|
||||
if(willOptionStartSession()) {
|
||||
if(!context.JK.guardAgainstBrowser(app)) {
|
||||
|
|
@ -915,6 +924,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
if(optionRequiresMultiplayerProfile()) {
|
||||
if(context.JK.guardAgainstSinglePlayerProfile(app).canPlay == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var valid = beforeMoveStep();
|
||||
if (!valid) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@
|
|||
var $screen = null;
|
||||
var $mixModeDropdown = null;
|
||||
var $templateMixerModeChange = null;
|
||||
|
||||
var $myTracksNoTracks = null;
|
||||
var $otherAudioContainer = null;
|
||||
var $myTracksContainer = null;
|
||||
var $liveTracksContainer = null;
|
||||
|
|
@ -121,6 +123,9 @@
|
|||
var $liveTracks = null;
|
||||
var $audioTracks = null;
|
||||
var $fluidTracks = null;
|
||||
var $voiceChat = null;
|
||||
var $openFtue = null;
|
||||
var $tracksHolder = null;
|
||||
|
||||
var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
|
||||
var muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
|
||||
|
|
@ -197,53 +202,85 @@
|
|||
// body-scoped drag handlers can go active
|
||||
screenActive = true;
|
||||
|
||||
gearUtils.guardAgainstInvalidConfiguration(app)
|
||||
.fail(function() {
|
||||
promptLeave = false;
|
||||
window.location = '/client#/home'
|
||||
})
|
||||
.done(function(){
|
||||
var result = sessionUtils.SessionPageEnter();
|
||||
rest.getSessionHistory(data.id)
|
||||
.done(function(musicSession) {
|
||||
|
||||
gearUtils.guardAgainstActiveProfileMissing(app, result)
|
||||
.fail(function(data) {
|
||||
var singlePlayerCheckOK = true;
|
||||
// to know whether we are allowed to be in this session, we have to check if we are the creator when checking against single player functionality
|
||||
if(musicSession.user_id != context.JK.currentUserId) {
|
||||
|
||||
var canPlay = context.JK.guardAgainstSinglePlayerProfile(app, function () {
|
||||
promptLeave = false;
|
||||
if(data && data.reason == 'handled') {
|
||||
if(data.nav == 'BACK') {
|
||||
window.history.go(-1);
|
||||
}
|
||||
else {
|
||||
window.location = data.nav;
|
||||
}
|
||||
}
|
||||
else {
|
||||
window.location = '/client#/home';
|
||||
}
|
||||
})
|
||||
.done(function(){
|
||||
});
|
||||
|
||||
sessionModel.waitForSessionPageEnterDone()
|
||||
.done(function(userTracks) {
|
||||
singlePlayerCheckOK = canPlay.canPlay;
|
||||
}
|
||||
if(singlePlayerCheckOK) {
|
||||
|
||||
context.JK.CurrentSessionModel.setUserTracks(userTracks);
|
||||
var shouldVerifyNetwork = musicSession.musician_access;
|
||||
gearUtils.guardAgainstInvalidConfiguration(app, shouldVerifyNetwork)
|
||||
.fail(function() {
|
||||
promptLeave = false;
|
||||
window.location = '/client#/home'
|
||||
})
|
||||
.done(function(){
|
||||
var result = sessionUtils.SessionPageEnter();
|
||||
|
||||
initializeSession();
|
||||
})
|
||||
.fail(function(data) {
|
||||
if(data == "timeout") {
|
||||
context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.')
|
||||
}
|
||||
else if(data == 'session_over') {
|
||||
// do nothing; session ended before we got the user track info. just bail
|
||||
}
|
||||
else {
|
||||
contetx.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data)
|
||||
}
|
||||
gearUtils.guardAgainstActiveProfileMissing(app, result)
|
||||
.fail(function(data) {
|
||||
promptLeave = false;
|
||||
if(data && data.reason == 'handled') {
|
||||
if(data.nav == 'BACK') {
|
||||
window.history.go(-1);
|
||||
}
|
||||
else {
|
||||
window.location = data.nav;
|
||||
}
|
||||
}
|
||||
else {
|
||||
window.location = '/client#/home';
|
||||
}
|
||||
})
|
||||
.done(function(){
|
||||
|
||||
sessionModel.waitForSessionPageEnterDone()
|
||||
.done(function(userTracks) {
|
||||
|
||||
context.JK.CurrentSessionModel.setUserTracks(userTracks);
|
||||
|
||||
initializeSession();
|
||||
})
|
||||
.fail(function(data) {
|
||||
if(data == "timeout") {
|
||||
context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.')
|
||||
}
|
||||
else if(data == 'session_over') {
|
||||
// do nothing; session ended before we got the user track info. just bail
|
||||
}
|
||||
else {
|
||||
context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data)
|
||||
}
|
||||
promptLeave = false;
|
||||
window.location = '/client#/home'
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
if(canPlay.dialog) {
|
||||
canPlay.dialog.one(EVENTS.DIALOG_CLOSED, function(e, data) {
|
||||
if(data.canceled) {
|
||||
promptLeave = false;
|
||||
window.location = '/client#/home'
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function notifyWithUserInfo(title , text, clientId) {
|
||||
|
|
@ -636,7 +673,6 @@
|
|||
function renderSession() {
|
||||
$myTracksContainer.empty();
|
||||
$('.session-track').remove(); // Remove previous tracks
|
||||
var $voiceChat = $('#voice-chat');
|
||||
$voiceChat.hide();
|
||||
_updateMixers();
|
||||
_renderTracks();
|
||||
|
|
@ -934,7 +970,6 @@
|
|||
if(voiceChatMixers) {
|
||||
var mixer = voiceChatMixers.mixer;
|
||||
|
||||
var $voiceChat = $('#voice-chat');
|
||||
$voiceChat.show();
|
||||
$voiceChat.attr('mixer-id', mixer.id);
|
||||
var $voiceChatGain = $voiceChat.find('.voicechat-gain');
|
||||
|
|
@ -1655,79 +1690,87 @@
|
|||
|
||||
var myTrack = app.clientId == participant.client_id;
|
||||
|
||||
// special case; if it's me and I have no tracks, show info about this sort of use of the app
|
||||
if (myTrack && participant.tracks.length == 0) {
|
||||
$tracksHolder.addClass('no-local-tracks')
|
||||
}
|
||||
else {
|
||||
$tracksHolder.removeClass('no-local-tracks')
|
||||
}
|
||||
|
||||
// loop through all tracks for each participant
|
||||
$.each(participant.tracks, function(index, track) {
|
||||
var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
|
||||
var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
|
||||
$.each(participant.tracks, function (index, track) {
|
||||
var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
|
||||
var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
|
||||
|
||||
// Default trackData to participant + no Mixer state.
|
||||
var trackData = {
|
||||
trackId: track.id,
|
||||
connection_id: track.connection_id,
|
||||
client_track_id: track.client_track_id,
|
||||
client_resource_id: track.client_resource_id,
|
||||
clientId: participant.client_id,
|
||||
name: name,
|
||||
instrumentIcon: instrumentIcon,
|
||||
avatar: photoUrl,
|
||||
latency: "good",
|
||||
gainPercent: 0,
|
||||
muteClass: 'muted',
|
||||
mixerId: "",
|
||||
avatarClass: 'avatar-med',
|
||||
preMasteredClass: "",
|
||||
myTrack: myTrack
|
||||
};
|
||||
// Default trackData to participant + no Mixer state.
|
||||
var trackData = {
|
||||
trackId: track.id,
|
||||
connection_id: track.connection_id,
|
||||
client_track_id: track.client_track_id,
|
||||
client_resource_id: track.client_resource_id,
|
||||
clientId: participant.client_id,
|
||||
name: name,
|
||||
instrumentIcon: instrumentIcon,
|
||||
avatar: photoUrl,
|
||||
latency: "good",
|
||||
gainPercent: 0,
|
||||
muteClass: 'muted',
|
||||
mixerId: "",
|
||||
avatarClass: 'avatar-med',
|
||||
preMasteredClass: "",
|
||||
myTrack: myTrack
|
||||
};
|
||||
|
||||
var mixerData = findMixerForTrack(participant.client_id, track, myTrack)
|
||||
var mixer = mixerData.mixer;
|
||||
var vuMixer = mixerData.vuMixer;
|
||||
var muteMixer = mixerData.muteMixer;
|
||||
var oppositeMixer = mixerData.oppositeMixer;
|
||||
var mixerData = findMixerForTrack(participant.client_id, track, myTrack)
|
||||
var mixer = mixerData.mixer;
|
||||
var vuMixer = mixerData.vuMixer;
|
||||
var muteMixer = mixerData.muteMixer;
|
||||
var oppositeMixer = mixerData.oppositeMixer;
|
||||
|
||||
|
||||
|
||||
if (mixer && oppositeMixer) {
|
||||
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
|
||||
if(!myTrack) {
|
||||
// it only makes sense to track 'audio established' for tracks that don't belong to you
|
||||
sessionModel.setAudioEstablished(participant.client_id, true);
|
||||
}
|
||||
|
||||
var gainPercent = percentFromMixerValue(
|
||||
mixer.range_low, mixer.range_high, mixer.volume_left);
|
||||
var muteClass = "enabled";
|
||||
if (mixer.mute) {
|
||||
muteClass = "muted";
|
||||
}
|
||||
|
||||
trackData.gainPercent = gainPercent;
|
||||
trackData.muteClass = muteClass;
|
||||
trackData.mixerId = mixer.id;
|
||||
trackData.vuMixerId = vuMixer.id;
|
||||
trackData.oppositeMixer = oppositeMixer;
|
||||
trackData.muteMixerId = muteMixer.id;
|
||||
trackData.noaudio = false;
|
||||
trackData.group_id = mixer.group_id;
|
||||
context.jamClient.SessionSetUserName(participant.client_id,name);
|
||||
|
||||
} else { // No mixer to match, yet
|
||||
lookingForMixers.push({track: track, clientId: participant.client_id})
|
||||
trackData.noaudio = true;
|
||||
if (!(lookingForMixersTimer)) {
|
||||
logger.debug("waiting for mixer to show up for track: " + track.id)
|
||||
lookingForMixersTimer = context.setInterval(lookForMixers, 500);
|
||||
}
|
||||
if (mixer && oppositeMixer) {
|
||||
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
|
||||
if (!myTrack) {
|
||||
// it only makes sense to track 'audio established' for tracks that don't belong to you
|
||||
sessionModel.setAudioEstablished(participant.client_id, true);
|
||||
}
|
||||
|
||||
var allowDelete = myTrack && index > 0;
|
||||
_addTrack(allowDelete, trackData, mixer, oppositeMixer);
|
||||
|
||||
// Show settings icons only for my tracks
|
||||
if (myTrack) {
|
||||
myTracks.push(trackData);
|
||||
var gainPercent = percentFromMixerValue(
|
||||
mixer.range_low, mixer.range_high, mixer.volume_left);
|
||||
var muteClass = "enabled";
|
||||
if (mixer.mute) {
|
||||
muteClass = "muted";
|
||||
}
|
||||
|
||||
trackData.gainPercent = gainPercent;
|
||||
trackData.muteClass = muteClass;
|
||||
trackData.mixerId = mixer.id;
|
||||
trackData.vuMixerId = vuMixer.id;
|
||||
trackData.oppositeMixer = oppositeMixer;
|
||||
trackData.muteMixerId = muteMixer.id;
|
||||
trackData.noaudio = false;
|
||||
trackData.group_id = mixer.group_id;
|
||||
context.jamClient.SessionSetUserName(participant.client_id, name);
|
||||
|
||||
} else { // No mixer to match, yet
|
||||
lookingForMixers.push({track: track, clientId: participant.client_id})
|
||||
trackData.noaudio = true;
|
||||
if (!(lookingForMixersTimer)) {
|
||||
logger.debug("waiting for mixer to show up for track: " + track.id)
|
||||
lookingForMixersTimer = context.setInterval(lookForMixers, 500);
|
||||
}
|
||||
}
|
||||
|
||||
var allowDelete = myTrack && index > 0;
|
||||
_addTrack(allowDelete, trackData, mixer, oppositeMixer);
|
||||
|
||||
// Show settings icons only for my tracks
|
||||
if (myTrack) {
|
||||
myTracks.push(trackData);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
configureTrackDialog = new context.JK.ConfigureTrackDialog(app, myTracks, sessionId, sessionModel);
|
||||
|
|
@ -1880,14 +1923,14 @@
|
|||
if (!(mixer.stereo)) { // mono track
|
||||
if (mixerId.substr(-4) === "_vul") {
|
||||
// Do the left
|
||||
selector = $('#tracks [mixer-id="' + pureMixerId + '_vul"]');
|
||||
selector = $tracksHolder.find('[mixer-id="' + pureMixerId + '_vul"]');
|
||||
context.JK.VuHelpers.updateVU(selector, value);
|
||||
// Do the right
|
||||
selector = $('#tracks [mixer-id="' + pureMixerId + '_vur"]');
|
||||
selector = $tracksHolder.find('[mixer-id="' + pureMixerId + '_vur"]');
|
||||
context.JK.VuHelpers.updateVU(selector, value);
|
||||
} // otherwise, it's a mono track, _vur event - ignore.
|
||||
} else { // stereo track
|
||||
selector = $('#tracks [mixer-id="' + mixerId + '"]');
|
||||
selector = $tracksHolder.find('[mixer-id="' + mixerId + '"]');
|
||||
context.JK.VuHelpers.updateVU(selector, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -2608,6 +2651,8 @@
|
|||
|
||||
var jamTrack = data.result.jamTrack;
|
||||
|
||||
$('.session-recording-name').text('');
|
||||
|
||||
// hide 'other audio' placeholder
|
||||
otherAudioFilled();
|
||||
|
||||
|
|
@ -3048,11 +3093,16 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
function showFTUEWhenNoInputs( ) {
|
||||
//app.afterFtue = function() { window.location.reload };
|
||||
app.layout.startNewFtue();
|
||||
}
|
||||
|
||||
function events() {
|
||||
$('#session-leave').on('click', sessionLeave);
|
||||
$('#session-resync').on('click', sessionResync);
|
||||
$('#session-contents').on("click", '[action="delete"]', deleteSession);
|
||||
$('#tracks').on('click', 'div[control="mute"]', toggleMute);
|
||||
$tracksHolder.on('click', 'div[control="mute"]', toggleMute);
|
||||
$('#recording-start-stop').on('click', startStopRecording);
|
||||
$('#open-a-recording').on('click', openRecording);
|
||||
$('#open-a-jamtrack').on('click', openJamTrack);
|
||||
|
|
@ -3061,11 +3111,24 @@
|
|||
$('#session-invite-musicians').on('click', inviteMusicians);
|
||||
$('#session-invite-musicians2').on('click', inviteMusicians);
|
||||
$('#track-settings').click(function() {
|
||||
|
||||
if(gearUtils.isNoInputProfile()) {
|
||||
// show FTUE
|
||||
showFTUEWhenNoInputs();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
configureTrackDialog.refresh();
|
||||
configureTrackDialog.showVoiceChatPanel(true);
|
||||
configureTrackDialog.showMusicAudioPanel(true);
|
||||
}
|
||||
});
|
||||
|
||||
$openFtue.click(function() {
|
||||
showFTUEWhenNoInputs();
|
||||
return false;
|
||||
})
|
||||
|
||||
$closePlaybackRecording.on('click', closeOpenMedia);
|
||||
$(playbackControls)
|
||||
.on('pause', onPause)
|
||||
|
|
@ -3106,6 +3169,8 @@
|
|||
$mixModeDropdown = $screen.find('select.monitor-mode')
|
||||
$templateMixerModeChange = $('#template-mixer-mode-change');
|
||||
$otherAudioContainer = $('#session-recordedtracks-container');
|
||||
$myTracksNoTracks = $('#session-mytracks-notracks')
|
||||
$openFtue = $screen.find('.open-ftue-no-tracks')
|
||||
$myTracksContainer = $('#session-mytracks-container')
|
||||
$liveTracksContainer = $('#session-livetracks-container');
|
||||
$closePlaybackRecording = $('#close-playback-recording')
|
||||
|
|
@ -3116,7 +3181,9 @@
|
|||
$myTracks = $screen.find('.session-mytracks');
|
||||
$liveTracks = $screen.find('.session-livetracks');
|
||||
$audioTracks = $screen.find('.session-recordings');
|
||||
$fluidTracks = $screen.find('.session-fluidtracks')
|
||||
$fluidTracks = $screen.find('.session-fluidtracks');
|
||||
$voiceChat = $screen.find('#voice-chat');
|
||||
$tracksHolder = $screen.find('#tracks')
|
||||
|
||||
events();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
var ALERT_TYPES = context.JK.ALERT_TYPES;
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var MIX_MODES = context.JK.MIX_MODES;
|
||||
var gearUtils = context.JK.GearUtilsInstance;
|
||||
|
||||
var userTracks = null; // comes from the backend
|
||||
var clientId = client.clientID;
|
||||
|
|
@ -213,7 +214,8 @@
|
|||
|
||||
// see if we already have tracks; if so, we need to run with these
|
||||
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
|
||||
if(inputTracks.length > 0) {
|
||||
|
||||
if(inputTracks.length > 0 || gearUtils.isNoInputProfile() ) {
|
||||
logger.debug("on page enter, tracks are already available")
|
||||
sessionPageEnterDeferred.resolve(inputTracks);
|
||||
var deferred = sessionPageEnterDeferred;
|
||||
|
|
|
|||
|
|
@ -136,18 +136,20 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
gearUtils.guardAgainstInvalidConfiguration(app)
|
||||
.fail(function() {
|
||||
app.notify(
|
||||
{ title: "Unable to Join Session",
|
||||
text: "You can only join a session once you have working audio gear and a tested internet connection."
|
||||
});
|
||||
})
|
||||
.done(function() {
|
||||
if (successCallback) {
|
||||
successCallback();
|
||||
}
|
||||
});
|
||||
if(context.JK.guardAgainstSinglePlayerProfile(app).canPlay) {
|
||||
gearUtils.guardAgainstInvalidConfiguration(app)
|
||||
.fail(function() {
|
||||
app.notify(
|
||||
{ title: "Unable to Join Session",
|
||||
text: "You can only join a session once you have working audio gear and a tested internet connection."
|
||||
});
|
||||
})
|
||||
.done(function() {
|
||||
if (successCallback) {
|
||||
successCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sessionUtils.joinSession = function(sessionId) {
|
||||
|
|
|
|||
|
|
@ -621,11 +621,12 @@
|
|||
}
|
||||
|
||||
// returns Fri May 20, 2013
|
||||
context.JK.formatDate = function (dateString) {
|
||||
context.JK.formatDate = function (dateString, suppressDay) {
|
||||
var date = new Date(dateString);
|
||||
return days[date.getDay()] + ' ' + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear();
|
||||
return (suppressDay ? '' : (days[date.getDay()] + ' ')) + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear();
|
||||
}
|
||||
|
||||
|
||||
context.JK.formatDateYYYYMMDD = function(dateString) {
|
||||
var date = new Date(dateString);
|
||||
return date.getFullYear() + '-' + context.JK.padString((date.getMonth() + 1).toString(), 2) + '-' + context.JK.padString(date.getDate(), 2);
|
||||
|
|
@ -1111,7 +1112,7 @@
|
|||
|
||||
context.JK.guardAgainstBrowser = function(app, args) {
|
||||
if(!gon.isNativeClient) {
|
||||
logger.debug("guarding against normal browser on screen thaht requires native client")
|
||||
logger.debug("guarding against normal browser on screen that requires native client")
|
||||
app.layout.showDialog('launch-app-dialog', args)
|
||||
.one(EVENTS.DIALOG_CLOSED, function() {
|
||||
if(args && args.goHome) {
|
||||
|
|
@ -1124,6 +1125,111 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
context.JK.guardAgainstSinglePlayerProfile = function(app, beforeCallback) {
|
||||
|
||||
var canPlayWithOthers = context.JK.GearUtilsInstance.canPlayWithOthers();
|
||||
|
||||
if(!canPlayWithOthers.canPlay) {
|
||||
logger.debug("guarding against single player profile")
|
||||
|
||||
var $dialog = app.layout.showDialog('single-player-profile-dialog');
|
||||
|
||||
// so that callers can check dialog result
|
||||
canPlayWithOthers.dialog = $dialog;
|
||||
|
||||
// allow callers to take action before default behavior
|
||||
if(beforeCallback) {
|
||||
$dialog.one(EVENTS.DIALOG_CLOSED, beforeCallback);
|
||||
}
|
||||
|
||||
$dialog.one(EVENTS.DIALOG_CLOSED, function(e, data) {
|
||||
|
||||
if(!data.canceled) {
|
||||
if(data.result.choice == 'private_session') {
|
||||
var data = {
|
||||
createType: 'quick-start',
|
||||
timezone: {},
|
||||
recurring_mode: {},
|
||||
language: {},
|
||||
band: {},
|
||||
musician_access: {},
|
||||
fans_access: {},
|
||||
rsvp_slots: [],
|
||||
open_rsvps: false
|
||||
};
|
||||
|
||||
context.JK.privateSessionSettings(data)
|
||||
|
||||
context.JK.createSession(app, data)
|
||||
.done(function(response) {
|
||||
var sessionId = response.id;
|
||||
|
||||
context.JK.GA.trackSessionCount(true, true, 0);
|
||||
|
||||
// we redirect to the session screen, which handles the REST call to POST /participants.
|
||||
logger.debug("joining session screen: " + sessionId)
|
||||
context.location = '/client#/session/' + sessionId;
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
logger.debug("unable to schedule a private session")
|
||||
app.notifyServerError(jqXHR, "Unable to schedule a private session");
|
||||
})
|
||||
}
|
||||
else if(data.result.choice == 'gear_setup') {
|
||||
window.location = '/client#/account/audio'
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("unknown choice: " + data.result.choice)
|
||||
alert("unknown choice: " + data.result.choice)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return canPlayWithOthers;
|
||||
}
|
||||
|
||||
context.JK.createSession = function(app, data) {
|
||||
|
||||
// auto pick an 'other' instrument
|
||||
var otherId = context.JK.server_to_client_instrument_map.Other.server_id; // get server ID
|
||||
var otherInstrumentInfo = context.JK.instrument_id_to_instrument[otherId]; // get display name
|
||||
var beginnerLevel = 1; // default to beginner
|
||||
var instruments = [ {id: otherId, name: otherInstrumentInfo.display, level: beginnerLevel} ];
|
||||
$.each(instruments, function(index, instrument) {
|
||||
var slot = {};
|
||||
slot.instrument_id = instrument.id;
|
||||
slot.proficiency_level = instrument.level;
|
||||
slot.approve = true;
|
||||
data.rsvp_slots.push(slot);
|
||||
});
|
||||
|
||||
data.isUnstructuredRsvp = true;
|
||||
|
||||
return rest.createScheduledSession(data)
|
||||
}
|
||||
|
||||
context.JK.privateSessionSettings = function(createSessionSettings) {
|
||||
createSessionSettings.genresValues = ['Pop'];
|
||||
createSessionSettings.genres = ['pop'];
|
||||
createSessionSettings.timezone = 'Central Time (US & Canada),America/Chicago'
|
||||
createSessionSettings.name = "Private Test Session";
|
||||
createSessionSettings.description = "Private session set up just to test things out in the session interface by myself.";
|
||||
createSessionSettings.notations = [];
|
||||
createSessionSettings.language = 'eng'
|
||||
createSessionSettings.legal_policy = 'Standard';
|
||||
createSessionSettings.musician_access = false
|
||||
createSessionSettings.fan_access = false
|
||||
createSessionSettings.fan_chat = false
|
||||
createSessionSettings.approval_required = false
|
||||
createSessionSettings.legal_terms = true
|
||||
createSessionSettings.recurring_mode = 'once';
|
||||
createSessionSettings.start = new Date().toDateString() + ' ' + context.JK.formatUtcTime(new Date(), false);
|
||||
createSessionSettings.duration = "60";
|
||||
createSessionSettings.open_rsvps = false
|
||||
createSessionSettings.rsvp_slots = [];
|
||||
}
|
||||
/*
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
|
||||
var rest = context.JK.Rest();
|
||||
var logger = context.JK.logger;
|
||||
|
||||
function initialize() {
|
||||
if(gon.signed_in) {
|
||||
window.location = "/client#/home"
|
||||
}
|
||||
}
|
||||
context.JK.HomePage = initialize;
|
||||
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
//= require web/sessions
|
||||
//= require web/session_info
|
||||
//= require web/recordings
|
||||
//= require web/welcome
|
||||
//= require web/home
|
||||
//= require web/individual_jamtrack
|
||||
//= require web/individual_jamtrack_band
|
||||
//= require fakeJamClient
|
||||
|
|
|
|||
|
|
@ -176,6 +176,10 @@
|
|||
wizard.setBackState(enabled);
|
||||
}
|
||||
|
||||
function moveToNext() {
|
||||
wizard.moveToNext();
|
||||
}
|
||||
|
||||
function setChosenInputs(_inputs) {
|
||||
inputs = _inputs;
|
||||
}
|
||||
|
|
@ -222,6 +226,7 @@
|
|||
this.getChosenInputs = getChosenInputs;
|
||||
this.setNextState = setNextState;
|
||||
this.setBackState = setBackState;
|
||||
this.moveToNext = moveToNext;
|
||||
this.initialize = initialize;
|
||||
this.createFTUEProfile = createFTUEProfile;
|
||||
this.getWizard = function() {return wizard; }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
var logger = context.JK.logger;
|
||||
var networkTest = new context.JK.NetworkTest(app);
|
||||
var $step = null;
|
||||
|
||||
// if not null and with in say 5 seconds, then the user is 'NEXT'ing too quickly. slow them down
|
||||
var clickFastTime = null;
|
||||
|
||||
function getLastNetworkFailAnalytics() {
|
||||
return networkTest.getLastNetworkFailure();
|
||||
|
|
@ -36,13 +37,29 @@
|
|||
initializeBackButtonState();
|
||||
}
|
||||
function initializeNextButtonState() {
|
||||
dialog.setNextState(networkTest.hasScoredNetworkSuccessfully() && !networkTest.isScoring());
|
||||
dialog.setNextState(!networkTest.isScoring());
|
||||
}
|
||||
|
||||
function initializeBackButtonState() {
|
||||
dialog.setBackState(!networkTest.isScoring());
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
// if we don't have a valid score, and if it's been less than 5 seconds since we've shown this step, slow the user down
|
||||
if (context.jamClient.GetNetworkTestScore() < 1 && userIsFastNexting()) {
|
||||
context.JK.Banner.showYesNo({
|
||||
html: "By clicking NEXT and skipping the test, you won't be able to play online in real-time sessions with others. Is this OK?",
|
||||
yes: function() {
|
||||
dialog.moveToNext();
|
||||
}});
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleHelp() {
|
||||
return "https://jamkazam.desk.com/customer/portal/articles/1716139-what-to-do-if-you-cannot-pass-the-network-test"
|
||||
//return "https://jamkazam.desk.com/customer/portal/articles/1599969-first-time-setup---step-6---test-your-network";
|
||||
|
|
@ -57,6 +74,16 @@
|
|||
networkTest.haltScoring();
|
||||
networkTest.cancel();
|
||||
updateButtons();
|
||||
watchForFastNexting();
|
||||
}
|
||||
|
||||
// fast nexting is a someone hitting next very quickly
|
||||
function watchForFastNexting() {
|
||||
clickFastTime = new Date();
|
||||
}
|
||||
|
||||
function userIsFastNexting() {
|
||||
return new Date().getTime() - clickFastTime.getTime() < 5000
|
||||
}
|
||||
|
||||
function beforeHide() {
|
||||
|
|
@ -77,6 +104,7 @@
|
|||
this.handleHelp = handleHelp;
|
||||
this.newSession = newSession;
|
||||
this.beforeHide = beforeHide;
|
||||
this.handleNext = handleNext;
|
||||
this.beforeShow = beforeShow;
|
||||
this.initialize = initialize;
|
||||
this.getLastNetworkFailAnalytics = getLastNetworkFailAnalytics;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
var $templateDeviceNotValid = null;
|
||||
var $resyncStatus = null;
|
||||
var $resyncStatusText = null;
|
||||
var $latencyScoreBox = null;
|
||||
var $highLatencyNotice = null;
|
||||
|
||||
|
||||
var operatingSystem = null;
|
||||
|
|
@ -579,6 +581,11 @@
|
|||
function initializeResync() {
|
||||
$resyncBtn.unbind('click').click(function () {
|
||||
|
||||
if($highLatencyNotice) {
|
||||
$highLatencyNotice.btOff()
|
||||
$highLatencyNotice = null;
|
||||
}
|
||||
|
||||
scheduleRescanSystem(function() {
|
||||
if (getSelectedInputs().length > 0 && getSelectedOutputs().length == 2) {
|
||||
logger.debug("after rescan, ready to attempt score")
|
||||
|
|
@ -946,6 +953,21 @@
|
|||
queueUpdateDeviceList = false;
|
||||
updateDeviceList();
|
||||
}
|
||||
|
||||
if(!data.validLatencyScore) {
|
||||
if (selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) {
|
||||
prodUserAboutHighLatency($latencyScoreBox, 'asio')
|
||||
}
|
||||
else if (selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) {
|
||||
prodUserAboutHighLatency($latencyScoreBox, 'asio')
|
||||
}
|
||||
else if (selectedDeviceInfo.input.info.type == 'MacOSX_builtin' || selectedDeviceInfo.output.info.type == 'MacOSX_builtin') {
|
||||
prodUserAboutHighLatency($latencyScoreBox, 'macosx-builtin')
|
||||
}
|
||||
else {
|
||||
prodUserAboutHighLatency($latencyScoreBox, 'generic')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLastAudioTestFailAnalytics() {
|
||||
|
|
@ -962,6 +984,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
function prodUserAboutHighLatency($btn, additional) {
|
||||
|
||||
setTimeout(function() {
|
||||
$highLatencyNotice = context.JK.prodBubble($btn, 'high-latency-notice', {additional: additional}, {duration: 20000, width:'400px', positions:['top']});
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function prodUserToTweakASIOSettings($btn) {
|
||||
setTimeout(function() {
|
||||
context.JK.prodBubble($btn, 'tweak-asio-settings', {}, {positions:['top']});
|
||||
|
|
@ -972,19 +1001,11 @@
|
|||
renderScoringStopped();
|
||||
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
|
||||
|
||||
if(data.reason == "latency") {
|
||||
if(data.reason == "io") {
|
||||
|
||||
console.log("selectedDeviceInfo", selectedDeviceInfo)
|
||||
if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) {
|
||||
prodUserToTweakASIOSettings($asioInputControlBtn)
|
||||
}
|
||||
else if(selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) {
|
||||
prodUserToTweakASIOSettings($asioOutputControlBtn)
|
||||
}
|
||||
|
||||
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
|
||||
}
|
||||
else if(data.reason = "io") {
|
||||
//storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
|
||||
|
||||
if(data.ioTarget == 'bad') {
|
||||
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.ioTarget, data.ioTargetScore);
|
||||
}
|
||||
|
|
@ -1210,6 +1231,7 @@
|
|||
$instructions = $step.find('.instructions');
|
||||
$resyncStatus = $step.find('.resync-status');
|
||||
$resyncStatusText = $step.find('.resynctext');
|
||||
$latencyScoreBox = $step.find('.latency-score-section')
|
||||
operatingSystem = context.JK.GetOSAsString();
|
||||
frameBuffers.initialize($knobs);
|
||||
$(frameBuffers)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
var GEAR_TEST_INVALIDATED_ASYNC = "gear_test.async_invalidated"; // happens when backend alerts us device is invalid
|
||||
|
||||
function isGoodFtue() {
|
||||
return validLatencyScore && validIOScore && !asynchronousInvalidDevice;
|
||||
return validIOScore && !asynchronousInvalidDevice;
|
||||
}
|
||||
|
||||
function processIOScore(io) {
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
|
||||
// now base the overall IO score based on both values.
|
||||
|
||||
$self.triggerHandler(GEAR_TEST_IO_DONE, {std:std, median:median, io:io, aggregrateIOClass: aggregrateIOClass, medianIOClass : medianIOClass, stdIOClass: stdIOClass})
|
||||
$self.triggerHandler(GEAR_TEST_IO_DONE, {std:std, median:median, io:io, aggregrateIOClass: aggregrateIOClass, medianIOClass : medianIOClass, stdIOClass: stdIOClass, validLatencyScore: validLatencyScore})
|
||||
//renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass);
|
||||
|
||||
if(aggregrateIOClass == "bad") {
|
||||
|
|
@ -103,10 +103,10 @@
|
|||
scoring = false;
|
||||
|
||||
if(isGoodFtue()) {
|
||||
$self.triggerHandler(GEAR_TEST_DONE)
|
||||
$self.triggerHandler(GEAR_TEST_DONE, {validLatencyScore: validLatencyScore})
|
||||
}
|
||||
else {
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std});
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std, validLatencyScore: validLatencyScore});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,11 +182,10 @@
|
|||
|
||||
updateScoreReport(latency, refocused);
|
||||
|
||||
// if there was a valid latency score, go on to the next step
|
||||
if (validLatencyScore) {
|
||||
if (true || validLatencyScore) {
|
||||
$self.triggerHandler(GEAR_TEST_IO_START);
|
||||
// reuse valid IO score if this is on refocus
|
||||
if(refocused && validIOScore) {
|
||||
if(false && (refocused && validIOScore)) {
|
||||
processIOScore(ioScore);
|
||||
}
|
||||
else {
|
||||
|
|
@ -215,12 +214,12 @@
|
|||
}
|
||||
else {
|
||||
scoring = false;
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', latencyScore: latencyScore.latency})
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', validLatencyScore: validLatencyScore, latencyScore: latencyScore.latency})
|
||||
}
|
||||
})
|
||||
.fail(function(ftueSaveResult) {
|
||||
scoring = false;
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'invalid_configuration', data: ftueSaveResult})
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'invalid_configuration', validLatencyScore: validLatencyScore, data: ftueSaveResult})
|
||||
})
|
||||
}, 250);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var SYSTEM_DEFAULT_PLAYBACK_ONLY = 'System Default (Playback Only)';
|
||||
|
||||
|
||||
context.JK.GearUtilsInstance = gearUtils;
|
||||
|
||||
|
|
@ -28,12 +30,42 @@
|
|||
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
|
||||
}
|
||||
|
||||
// to play with others, you have to have inputs,
|
||||
// as well have a score below 20 ms
|
||||
gearUtils.canPlayWithOthers = function(profile) {
|
||||
|
||||
gearUtils.createProfileName = function(deviceInfo, chatName) {
|
||||
var isNoInputProfile = gearUtils.isNoInputProfile(profile);
|
||||
var expectedLatency = context.jamClient.FTUEGetExpectedLatency();
|
||||
var audioLatency = expectedLatency ? expectedLatency.latency : null;
|
||||
var highLatency = audioLatency > 20;
|
||||
var networkScore = context.jamClient.GetNetworkTestScore();
|
||||
var badNetworkScore = networkScore < 2;
|
||||
|
||||
return {
|
||||
canPlay: !isNoInputProfile && !highLatency,
|
||||
isNoInputProfile: isNoInputProfile,
|
||||
badNetworkScore: badNetworkScore,
|
||||
highLatency: highLatency,
|
||||
audioLatency: audioLatency,
|
||||
networkScore: networkScore,
|
||||
}
|
||||
}
|
||||
|
||||
gearUtils.isNoInputProfile = function(profile) {
|
||||
if (profile === undefined) {
|
||||
profile = context.jamClient.FTUEGetMusicProfileName();
|
||||
}
|
||||
|
||||
if(profile == SYSTEM_DEFAULT_PLAYBACK_ONLY) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
gearUtils.createProfileName = function (deviceInfo, chatName) {
|
||||
var isSameInOut = deviceInfo.input.id == deviceInfo.output.id;
|
||||
|
||||
var name = null;
|
||||
if(isSameInOut) {
|
||||
if (isSameInOut) {
|
||||
name = "In/Out: " + deviceInfo.input.info.displayName;
|
||||
}
|
||||
else {
|
||||
|
|
@ -45,19 +77,19 @@
|
|||
}
|
||||
|
||||
|
||||
gearUtils.selectedDeviceInfo = function(audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
|
||||
gearUtils.selectedDeviceInfo = function (audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
|
||||
|
||||
|
||||
if(!audioInputDeviceId) {
|
||||
if (!audioInputDeviceId) {
|
||||
logger.debug("gearUtils.selectedDeviceInfo: no active input device");
|
||||
return null;
|
||||
}
|
||||
if(!audioOutputDeviceId) {
|
||||
if (!audioOutputDeviceId) {
|
||||
logger.debug("gearUtils.selectedDeviceInfo: no active output device");
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!deviceInformation) {
|
||||
if (!deviceInformation) {
|
||||
deviceInformation = gearUtils.loadDeviceInfo();
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +113,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
gearUtils.loadDeviceInfo = function() {
|
||||
gearUtils.loadDeviceInfo = function () {
|
||||
|
||||
var operatingSystem = context.JK.GetOSAsString();
|
||||
// should return one of:
|
||||
|
|
@ -128,6 +160,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (device.name == "JamKazam Virtual Input") {
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceInfo = {};
|
||||
|
||||
deviceInfo.id = device.guid;
|
||||
|
|
@ -145,22 +181,22 @@
|
|||
return loadedDevices;
|
||||
}
|
||||
|
||||
gearUtils.updateDefaultBuffers = function(selectedDeviceInfo, frameBuffers) {
|
||||
gearUtils.updateDefaultBuffers = function (selectedDeviceInfo, frameBuffers) {
|
||||
function hasWDMAssociated() {
|
||||
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
|
||||
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
|
||||
}
|
||||
|
||||
function hasASIOAssociated() {
|
||||
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio')
|
||||
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio')
|
||||
}
|
||||
|
||||
// handle specific framesize settings
|
||||
if(hasWDMAssociated() || hasASIOAssociated()) {
|
||||
if (hasWDMAssociated() || hasASIOAssociated()) {
|
||||
var framesize = frameBuffers.selectedFramesize();
|
||||
|
||||
if(framesize == 2.5) {
|
||||
if (framesize == 2.5) {
|
||||
// if there is a WDM device, start off at 1/1 due to empirically observed issues with 0/0
|
||||
if(hasWDMAssociated()) {
|
||||
if (hasWDMAssociated()) {
|
||||
logger.debug("setting default buffers to 1/1");
|
||||
frameBuffers.setBufferIn('1');
|
||||
frameBuffers.setBufferOut('1');
|
||||
|
|
@ -172,15 +208,15 @@
|
|||
frameBuffers.setBufferOut('0');
|
||||
}
|
||||
}
|
||||
else if(framesize == 5) {
|
||||
else if (framesize == 5) {
|
||||
logger.debug("setting default buffers to 3/2");
|
||||
frameBuffers.setBufferIn('3');
|
||||
frameBuffers.setBufferOut('2');
|
||||
}
|
||||
else {
|
||||
logger.debug("setting default buffers to 6/5");
|
||||
frameBuffers.setBufferIn('6');
|
||||
frameBuffers.setBufferOut('5');
|
||||
logger.debug("setting default buffers to 2/2");
|
||||
frameBuffers.setBufferIn('2');
|
||||
frameBuffers.setBufferOut('2');
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -193,7 +229,7 @@
|
|||
context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
|
||||
}
|
||||
|
||||
gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
|
||||
gearUtils.ftueSummary = function (operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
|
||||
return {
|
||||
os: operatingSystem,
|
||||
version: context.jamClient.ClientUpdateVersion(),
|
||||
|
|
@ -203,7 +239,7 @@
|
|||
validLatencyScore: gearTest.isValidLatencyScore(),
|
||||
validIOScore: gearTest.isValidIOScore(),
|
||||
latencyScore: gearTest.getLatencyScore(),
|
||||
ioScore : gearTest.getIOScore(),
|
||||
ioScore: gearTest.getIOScore(),
|
||||
},
|
||||
audioParameters: {
|
||||
frameSize: frameBuffers.selectedFramesize(),
|
||||
|
|
@ -221,21 +257,21 @@
|
|||
* This is to provide a unified view of FTUEGetAllAudioConfigurations & FTUEGetGoodAudioConfigurations
|
||||
* @returns an array of profiles, where each profile is: {id: profile-name, good: boolean, class: 'bad' | 'good', current: boolean }
|
||||
*/
|
||||
gearUtils.getProfiles = function() {
|
||||
gearUtils.getProfiles = function () {
|
||||
var all = context.jamClient.FTUEGetAllAudioConfigurations();
|
||||
var good = context.jamClient.FTUEGetGoodAudioConfigurations();
|
||||
var current = context.jamClient.LastUsedProfileName();
|
||||
|
||||
var profiles = [];
|
||||
context._.each(all, function(item) {
|
||||
context._.each(all, function (item) {
|
||||
|
||||
profiles.push({id: item, good: false, class:'bad', current: current == item})
|
||||
profiles.push({id: item, good: false, class: 'bad', current: current == item})
|
||||
});
|
||||
|
||||
if(good) {
|
||||
for(var i = 0; i < good.length; i++) {
|
||||
for(var j = 0; j < profiles.length; j++) {
|
||||
if(good[i] == profiles[j].id) {
|
||||
if (good) {
|
||||
for (var i = 0; i < good.length; i++) {
|
||||
for (var j = 0; j < profiles.length; j++) {
|
||||
if (good[i] == profiles[j].id) {
|
||||
profiles[j].good = true;
|
||||
profiles[j].class = 'good';
|
||||
break;
|
||||
|
|
@ -246,21 +282,21 @@
|
|||
|
||||
return profiles;
|
||||
}
|
||||
gearUtils.postDiagnostic = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
|
||||
gearUtils.postDiagnostic = function (operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
|
||||
rest.createDiagnostic({
|
||||
type: 'GEAR_SELECTION',
|
||||
data: {
|
||||
client_type: context.JK.clientType(),
|
||||
client_id:
|
||||
context.JK.JamServer.clientID,
|
||||
summary:gearUtils.ftueSummary(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated)}
|
||||
client_id: context.JK.JamServer.clientID,
|
||||
summary: gearUtils.ftueSummary(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// complete list of possibly chatInputs, whether currently assigned as the chat channel or not
|
||||
// each item should be {id: channelId, name: channelName, assignment: channel assignment}
|
||||
|
||||
gearUtils.getChatInputs = function(){
|
||||
gearUtils.getChatInputs = function () {
|
||||
|
||||
var musicPorts = jamClient.FTUEGetChannels();
|
||||
//var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true);
|
||||
|
|
@ -273,11 +309,11 @@
|
|||
|
||||
var deDupper = {};
|
||||
|
||||
context._.each(musicPorts.inputs, function(input) {
|
||||
context._.each(musicPorts.inputs, function (input) {
|
||||
|
||||
var chatInput = {id: input.id, name: input.name, assignment:input.assignment};
|
||||
if(!deDupper[input.id]) {
|
||||
if(input.assignment <= 0) {
|
||||
var chatInput = {id: input.id, name: input.name, assignment: input.assignment};
|
||||
if (!deDupper[input.id]) {
|
||||
if (input.assignment <= 0) {
|
||||
chatInputs.push(chatInput);
|
||||
deDupper[input.id] = chatInput;
|
||||
}
|
||||
|
|
@ -295,9 +331,9 @@
|
|||
}
|
||||
})*/
|
||||
|
||||
context._.each(chatsOnOtherDevices, function(chatChannelName, chatChannelId) {
|
||||
context._.each(chatsOnOtherDevices, function (chatChannelName, chatChannelId) {
|
||||
var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null};
|
||||
if(!deDupper[chatInput.id]) {
|
||||
if (!deDupper[chatInput.id]) {
|
||||
var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true);
|
||||
chatInput.assignment = assignment;
|
||||
|
||||
|
|
@ -309,11 +345,11 @@
|
|||
return chatInputs;
|
||||
}
|
||||
|
||||
gearUtils.isChannelAvailableForChat = function(chatChannelId, musicPorts) {
|
||||
gearUtils.isChannelAvailableForChat = function (chatChannelId, musicPorts) {
|
||||
var result = true;
|
||||
context._.each(musicPorts.inputs, function(inputChannel) {
|
||||
context._.each(musicPorts.inputs, function (inputChannel) {
|
||||
// if the channel is currently assigned to a track, it not unassigned
|
||||
if(inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) {
|
||||
if (inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) {
|
||||
result = false;
|
||||
return false; // break
|
||||
}
|
||||
|
|
@ -324,13 +360,13 @@
|
|||
|
||||
// if the user has a good user network score, immediately returns with a resolved deferred object.
|
||||
// if not, the user will have the network test dialog prompted... once it's closed, then you'll be told reject() if score is still bad, or resolve() if now good
|
||||
gearUtils.guardAgainstBadNetworkScore = function(app) {
|
||||
gearUtils.guardAgainstBadNetworkScore = function (app) {
|
||||
var deferred = new $.Deferred();
|
||||
|
||||
if (!gearUtils.validNetworkScore()) {
|
||||
// invalid network test score. They have to score to move on
|
||||
app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, function() {
|
||||
if(gearUtils.validNetworkScore()) {
|
||||
app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, function () {
|
||||
if (gearUtils.validNetworkScore()) {
|
||||
deferred.resolve();
|
||||
}
|
||||
else {
|
||||
|
|
@ -346,19 +382,19 @@
|
|||
|
||||
// XXX this isn't quite right... it needs to check if a good device is *active*
|
||||
// but seen too many problems so far with the backend not reporting any profile active
|
||||
gearUtils.hasGoodActiveProfile = function(verifyTracks) {
|
||||
gearUtils.hasGoodActiveProfile = function (verifyTracks) {
|
||||
var hasOneConfigureDevice = context.JK.hasOneConfiguredDevice();
|
||||
logger.debug("hasGoodActiveProfile: " + hasOneConfigureDevice ? "devices='has at least one configured device' " : "devices='has no configured device' ")
|
||||
return hasOneConfigureDevice;
|
||||
}
|
||||
|
||||
// if the user does not have any profiles, show the FTUE
|
||||
gearUtils.guardAgainstInvalidGearConfiguration = function(app) {
|
||||
gearUtils.guardAgainstInvalidGearConfiguration = function (app) {
|
||||
var deferred = new $.Deferred();
|
||||
|
||||
if (context.jamClient.FTUEGetAllAudioConfigurations().length == 0) {
|
||||
app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, function() {
|
||||
if(gearUtils.hasGoodActiveProfile() && gearUtils.validNetworkScore()) {
|
||||
app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, function () {
|
||||
if (gearUtils.hasGoodActiveProfile() && gearUtils.validNetworkScore()) {
|
||||
deferred.resolve();
|
||||
}
|
||||
else {
|
||||
|
|
@ -373,27 +409,27 @@
|
|||
return deferred;
|
||||
}
|
||||
|
||||
gearUtils.guardAgainstActiveProfileMissing = function(app, backendInfo) {
|
||||
gearUtils.guardAgainstActiveProfileMissing = function (app, backendInfo) {
|
||||
|
||||
|
||||
var deferred = new $.Deferred();
|
||||
logger.debug("guardAgainstActiveProfileMissing: backendInfo %o", backendInfo);
|
||||
if(backendInfo.error && backendInfo['reason'] == 'no_profile' && context.jamClient.FTUEGetAllAudioConfigurations().length > 0) {
|
||||
if (backendInfo.error && backendInfo['reason'] == 'no_profile' && context.jamClient.FTUEGetAllAudioConfigurations().length > 0) {
|
||||
// if the backend says we have no_profile, but we have profiles , send them to the audio profile screen
|
||||
// this should be a very rare path
|
||||
deferred.reject({reason:'handled', nav: '/client#/account/audio'});
|
||||
deferred.reject({reason: 'handled', nav: '/client#/account/audio'});
|
||||
context.JK.Banner.showAlert('No Active Profile', 'We\'ve sent you to the audio profile screen to remedy the fact that you have no active audio profile. Please select ACTIVATE on an existing profile, or select ADD NEW GEAR to add a new profile.');
|
||||
}
|
||||
else if (backendInfo.error && backendInfo['reason'] == 'device_failure') {
|
||||
app.layout.showDialog('audio-profile-invalid-dialog')
|
||||
.one(EVENTS.DIALOG_CLOSED, function(e, data) {
|
||||
if(!data.result || data.result == 'cancel') {
|
||||
deferred.reject({reason:'handled', nav: 'BACK'});
|
||||
.one(EVENTS.DIALOG_CLOSED, function (e, data) {
|
||||
if (!data.result || data.result == 'cancel') {
|
||||
deferred.reject({reason: 'handled', nav: 'BACK'});
|
||||
}
|
||||
else if(data.result == 'configure_gear'){
|
||||
deferred.reject({reason:'handled', nav: '/client#/account/audio'});
|
||||
else if (data.result == 'configure_gear') {
|
||||
deferred.reject({reason: 'handled', nav: '/client#/account/audio'});
|
||||
}
|
||||
else if(data.result == 'session') {
|
||||
else if (data.result == 'session') {
|
||||
deferred.resolve();
|
||||
}
|
||||
else {
|
||||
|
|
@ -409,43 +445,49 @@
|
|||
}
|
||||
|
||||
// tests both device config, and network score
|
||||
gearUtils.guardAgainstInvalidConfiguration = function(app) {
|
||||
gearUtils.guardAgainstInvalidConfiguration = function (app, verifyNetworkScore) {
|
||||
var deferred = new $.Deferred();
|
||||
gearUtils.guardAgainstInvalidGearConfiguration(app)
|
||||
.fail(function() {
|
||||
.fail(function () {
|
||||
deferred.reject();
|
||||
})
|
||||
.done(function() {
|
||||
gearUtils.guardAgainstBadNetworkScore(app)
|
||||
.fail(function() {
|
||||
deferred.reject();
|
||||
})
|
||||
.done(function() {
|
||||
deferred.resolve();
|
||||
})
|
||||
.done(function () {
|
||||
if(verifyNetworkScore) {
|
||||
gearUtils.guardAgainstBadNetworkScore(app)
|
||||
.fail(function () {
|
||||
deferred.reject();
|
||||
})
|
||||
.done(function () {
|
||||
deferred.resolve();
|
||||
})
|
||||
}
|
||||
else {
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
gearUtils.skipNetworkTest = function() {
|
||||
gearUtils.skipNetworkTest = function () {
|
||||
context.jamClient.SetNetworkTestScore(gearUtils.SKIPPED_NETWORK_TEST);
|
||||
gearUtils.skippedNetworkTest = true;
|
||||
}
|
||||
|
||||
gearUtils.isNetworkTestSkipped = function() {
|
||||
gearUtils.isNetworkTestSkipped = function () {
|
||||
return gearUtils.skippedNetworkTest;
|
||||
}
|
||||
|
||||
gearUtils.validNetworkScore = function() {
|
||||
gearUtils.validNetworkScore = function () {
|
||||
return gearUtils.skippedNetworkTest || context.jamClient.GetNetworkTestScore() >= 2;
|
||||
}
|
||||
|
||||
gearUtils.isRestartingAudio = function() {
|
||||
gearUtils.isRestartingAudio = function () {
|
||||
return !!reloadAudioTimeout;
|
||||
}
|
||||
|
||||
gearUtils.scheduleAudioRestart = function(location, initial_delay, beforeScan, afterScan, cancelScan) {
|
||||
gearUtils.scheduleAudioRestart = function (location, initial_delay, beforeScan, afterScan, cancelScan) {
|
||||
|
||||
logger.debug("scheduleAudioRestart: (from " + location + ")")
|
||||
|
||||
|
|
@ -453,40 +495,42 @@
|
|||
|
||||
function clearAudioReloadTimer() {
|
||||
|
||||
if(!cancellable) {return;}
|
||||
if (!cancellable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(cancelScan) {
|
||||
if (cancelScan) {
|
||||
cancelScan();
|
||||
}
|
||||
else if(afterScan) {
|
||||
else if (afterScan) {
|
||||
afterScan(true);
|
||||
}
|
||||
|
||||
clearTimeout(reloadAudioTimeout);
|
||||
reloadAudioTimeout = null;
|
||||
currentAudioRestartLocation = null;
|
||||
currentAudioRestartLocation = null;
|
||||
cancellable = false;
|
||||
}
|
||||
|
||||
// refresh timer if outstanding
|
||||
if(reloadAudioTimeout) {
|
||||
if (reloadAudioTimeout) {
|
||||
logger.debug("scheduleAudioRestart: clearing timeout (from " + location + ")")
|
||||
clearTimeout(reloadAudioTimeout);
|
||||
}
|
||||
|
||||
currentAudioRestartLocation = location;
|
||||
|
||||
if(beforeScan) {
|
||||
if (beforeScan) {
|
||||
beforeScan();
|
||||
}
|
||||
|
||||
reloadAudioTimeout = setTimeout(function() {
|
||||
reloadAudioTimeout = setTimeout(function () {
|
||||
logger.debug("scheduleAudioRestart: rescan beginning (from " + location + ")")
|
||||
reloadAudioTimeout = null;
|
||||
currentAudioRestartLocation = null;
|
||||
cancellable = false;
|
||||
|
||||
if(afterScan) {
|
||||
if (afterScan) {
|
||||
afterScan(false);
|
||||
}
|
||||
}, initial_delay ? initial_delay : 5000);
|
||||
|
|
@ -494,4 +538,45 @@
|
|||
return clearAudioReloadTimer;
|
||||
}
|
||||
|
||||
gearUtils.bootstrapDefaultPlaybackProfile = function () {
|
||||
|
||||
var profiles = gearUtils.getProfiles();
|
||||
|
||||
var foundSystemDefaultPlaybackOnly = false
|
||||
context._.each(profiles, function (profile) {
|
||||
if (profile.id == SYSTEM_DEFAULT_PLAYBACK_ONLY) {
|
||||
foundSystemDefaultPlaybackOnly = true
|
||||
return false;
|
||||
}
|
||||
})
|
||||
|
||||
if (!foundSystemDefaultPlaybackOnly) {
|
||||
logger.debug("creating system default profile (playback only")
|
||||
if(!gearUtils.createDefaultPlaybackOnlyProfile()) {
|
||||
logger.error("unable to create the default playback profile!");
|
||||
}
|
||||
}
|
||||
}
|
||||
gearUtils.createDefaultPlaybackOnlyProfile = function () {
|
||||
|
||||
var eMixerInputSampleRate = {
|
||||
JAMKAZAM_AUTO_SR: 0,
|
||||
USE_DEVICE_DEFAULT_SR: 1,
|
||||
PREFER_44: 2,
|
||||
PREFER_48: 3,
|
||||
PREFER_96: 4
|
||||
}
|
||||
|
||||
// null//upgrade protect
|
||||
if(context.jamClient.FTUECreateUpdatePlayBackProfile) {
|
||||
return context.jamClient.FTUECreateUpdatePlayBackProfile(SYSTEM_DEFAULT_PLAYBACK_ONLY,
|
||||
eMixerInputSampleRate.JAMKAZAM_AUTO_SR,
|
||||
0, // buffering
|
||||
false); // start audio
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -69,11 +69,15 @@
|
|||
if(result === false) {return false;}
|
||||
}
|
||||
|
||||
moveToNext();
|
||||
return false;
|
||||
}
|
||||
|
||||
function moveToNext() {
|
||||
previousStep = step;
|
||||
step = step + 1;
|
||||
|
||||
moveToStep();
|
||||
return false;
|
||||
}
|
||||
|
||||
function help() {
|
||||
|
|
@ -238,6 +242,7 @@
|
|||
this.getNextButton = getNextButton;
|
||||
this.setNextState = setNextState;
|
||||
this.setBackState = setBackState;
|
||||
this.moveToNext = moveToNext;
|
||||
this.getCurrentStep = getCurrentStep;
|
||||
this.getCurrentWizardStep = getCurrentWizardStep;
|
||||
this.onCloseDialog = onCloseDialog;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
@import 'common.css.scss';
|
||||
|
||||
#account-payment-history {
|
||||
|
||||
.content-body-scroller {
|
||||
padding:20px;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
table td.loading {
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.end-of-list {
|
||||
margin-top:20px;
|
||||
}
|
||||
td {
|
||||
|
||||
&.amount {
|
||||
}
|
||||
|
||||
&.voided {
|
||||
|
||||
text-decoration:line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.account-left {
|
||||
float: left;
|
||||
min-width: 165px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.account-left h2 {
|
||||
color: #FFFFFF;
|
||||
font-size: 23px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input-aligner {
|
||||
margin: 10px 14px 20px 0;
|
||||
text-align:right;
|
||||
|
||||
.back {
|
||||
margin-right:22px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
*= require ./findSession
|
||||
*= require ./session
|
||||
*= require ./account
|
||||
*= require ./accountPaymentHistory
|
||||
*= require ./search
|
||||
*= require ./ftue
|
||||
*= require ./jamServer
|
||||
|
|
|
|||
|
|
@ -330,3 +330,8 @@ $fair: #cc9900;
|
|||
border-radius:8px;
|
||||
}
|
||||
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
.retry {
|
||||
margin-top:10px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.msg {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ body.jam, body.web, .dialog{
|
|||
}
|
||||
}
|
||||
|
||||
.help-high-latency-notice {
|
||||
width:400px;
|
||||
}
|
||||
|
||||
.help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks {
|
||||
|
||||
font-size:12px;
|
||||
|
|
|
|||
|
|
@ -240,8 +240,4 @@
|
|||
|
||||
.jamtrack_buttons {
|
||||
margin: 8px 4px 12px 4px;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize
|
||||
}
|
||||
|
|
@ -68,8 +68,8 @@
|
|||
vertical-align:top;
|
||||
}
|
||||
|
||||
.session-recordedtracks-container {
|
||||
//display: block;
|
||||
.recording {
|
||||
top:310px; // // this is to prevent scroll bars from pushing this element up
|
||||
}
|
||||
|
||||
.recording-controls {
|
||||
|
|
@ -84,6 +84,32 @@
|
|||
.recording-current {
|
||||
top:3px ! important;
|
||||
}
|
||||
|
||||
.jam-track-get-ready {
|
||||
display:none;
|
||||
position:absolute;
|
||||
top:-29px;
|
||||
margin-left:-50px;
|
||||
width:100px;
|
||||
vertical-align:middle;
|
||||
height:32px;
|
||||
line-height:32px;
|
||||
left:50%;
|
||||
|
||||
&[data-mode="JAMTRACK"] {
|
||||
&[data-current-time="0"] {
|
||||
display:block;
|
||||
}
|
||||
}
|
||||
.spinner-small {
|
||||
vertical-align:middle;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align:middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playback-mode-buttons {
|
||||
|
|
@ -210,6 +236,18 @@
|
|||
$otheraudio-minwidth:195px;
|
||||
$otheraudio-open-minwidth:230px;
|
||||
|
||||
#session-mytracks-notracks {
|
||||
display:none;
|
||||
|
||||
p {
|
||||
font-size:14px;
|
||||
white-space:normal;
|
||||
margin:10px 10px 0 0;
|
||||
line-height:125%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.session-mytracks {
|
||||
padding-left:15px;
|
||||
float:left;
|
||||
|
|
@ -251,6 +289,12 @@
|
|||
.recording-controls {
|
||||
min-width:230px;
|
||||
}
|
||||
|
||||
#recording-start-stop {
|
||||
@include border-radius(4px);
|
||||
padding-left:5px;
|
||||
padding-right:5px;
|
||||
}
|
||||
}
|
||||
|
||||
.session-recordings {
|
||||
|
|
@ -358,6 +402,25 @@
|
|||
#tracks {
|
||||
margin-top:12px;
|
||||
overflow:auto;
|
||||
|
||||
&.no-local-tracks {
|
||||
|
||||
#session-mytracks-notracks {
|
||||
display:block;
|
||||
}
|
||||
|
||||
#session-mytracks-container {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#recording-start-stop {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#session-invite-musicians {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.track-empty a {
|
||||
|
|
|
|||
|
|
@ -429,8 +429,12 @@
|
|||
|
||||
}
|
||||
|
||||
.instructions {
|
||||
height: 228px !important;
|
||||
}
|
||||
|
||||
.network-test-results {
|
||||
height: 248px !important;
|
||||
height: 228px !important;
|
||||
@include border_box_sizing;
|
||||
&.testing {
|
||||
|
||||
|
|
|
|||
|
|
@ -135,6 +135,25 @@
|
|||
}
|
||||
|
||||
|
||||
.get-a-free-jamtrack-section {
|
||||
|
||||
&.has-free-jamtrack {
|
||||
h2.get-a-free-jamtrack {
|
||||
display:block;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top:-7px;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-free-jamtrack {
|
||||
h2.browse-jamtracks {
|
||||
display:block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ftue-inner table a {
|
||||
text-decoration:none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
#single-player-profile-dialog {
|
||||
|
||||
.dialog-inner {
|
||||
|
||||
&.high-latency {
|
||||
.high-latency {
|
||||
display:block
|
||||
}
|
||||
}
|
||||
|
||||
&.has-no-inputs {
|
||||
.has-no-inputs {
|
||||
display:block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-latency {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin:20px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height:125%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@import "client/common.css.scss";
|
||||
|
||||
body.web.home {
|
||||
|
||||
.landing-tag {
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
.logo-home {
|
||||
margin:20px 0 15px;
|
||||
}
|
||||
|
||||
.home-column {
|
||||
margin-top: 20px;
|
||||
width: 345px;
|
||||
float: left;
|
||||
margin-right: 30px;
|
||||
text-align: center;
|
||||
margin-bottom:35px;
|
||||
|
||||
&.last{
|
||||
margin-right:0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
text-align:left;
|
||||
margin-top:12px;
|
||||
margin-bottom:6px;
|
||||
font-size:18px;
|
||||
font-weight:700;
|
||||
}
|
||||
p {
|
||||
color:white;
|
||||
text-align:left;
|
||||
line-height:120%;
|
||||
font-size:13px;
|
||||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
.extra-links {
|
||||
width:234px;
|
||||
display:inline-block;
|
||||
}
|
||||
.learn-more {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
|
||||
&.shared {
|
||||
float:left;
|
||||
margin-left:10px;
|
||||
}
|
||||
}
|
||||
|
||||
.sign-in-holder {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
|
||||
&.shared {
|
||||
float:right;
|
||||
margin-right:10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.latest-promo {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.endorsement-promo {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.home-buzz {
|
||||
|
||||
h2 {
|
||||
width:100%;
|
||||
text-align:center;
|
||||
margin:20px 0;
|
||||
}
|
||||
width: 300px;
|
||||
position:relative;
|
||||
margin-right:20px;
|
||||
.buzz-items {
|
||||
.buzz-item {
|
||||
padding: 12px 0;
|
||||
&:last-child {
|
||||
padding-bottom:0;
|
||||
}
|
||||
}
|
||||
.buzz-item-text {
|
||||
padding-left: 78px; // 58px width for image + 20px margin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.latest {
|
||||
width: 750px;
|
||||
position:relative;
|
||||
top:-45px;
|
||||
|
||||
.home-session-list {
|
||||
top:5px; // XXX remove post release
|
||||
width:100%;
|
||||
height:400px;
|
||||
border: solid 1px #ed3618;
|
||||
background-color:#353535;
|
||||
float:left;
|
||||
overflow:hidden;
|
||||
position:relative;
|
||||
|
||||
}
|
||||
.latest-head {
|
||||
position: absolute;
|
||||
padding:20px 20px 12px;
|
||||
height: 53px;
|
||||
width:inherit;
|
||||
}
|
||||
|
||||
.latest-body {
|
||||
width:100%;
|
||||
top:65px;
|
||||
bottom:0;
|
||||
position:absolute;
|
||||
overflow-y:scroll;
|
||||
@include border_box_sizing;
|
||||
|
||||
.session-list-wrapper {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
*= require web/footer
|
||||
*= require web/recordings
|
||||
*= require web/welcome
|
||||
*= require web/home
|
||||
#= require web/sessions
|
||||
*= require web/events
|
||||
*= require web/session_info
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@import "client/common.css.scss";
|
||||
|
||||
body.web {
|
||||
body.web.welcome {
|
||||
|
||||
.signin-common {
|
||||
height:auto;
|
||||
|
|
|
|||
|
|
@ -18,27 +18,27 @@ class ApiRecurlyController < ApiController
|
|||
|
||||
|
||||
if current_user
|
||||
# keep reuse card up-to-date
|
||||
User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card_next_time])
|
||||
# keep reuse card up-to-date
|
||||
User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card_next_time])
|
||||
else
|
||||
options = {
|
||||
remote_ip: request.remote_ip,
|
||||
first_name: billing_info[:first_name],
|
||||
last_name: billing_info[:last_name],
|
||||
email: params[:email],
|
||||
password: params[:password],
|
||||
password_confirmation: params[:password],
|
||||
terms_of_service: terms_of_service,
|
||||
instruments: [{ :instrument_id => 'other', :proficiency_level => 1, :priority => 1 }],
|
||||
birth_date: nil,
|
||||
location: { :country => billing_info[:country], :state => billing_info[:state], :city => billing_info[:city]},
|
||||
musician: true,
|
||||
skip_recaptcha: true,
|
||||
invited_user: nil,
|
||||
fb_signup: nil,
|
||||
signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm",
|
||||
any_user: any_user,
|
||||
reuse_card: reuse_card_next_time
|
||||
remote_ip: request.remote_ip,
|
||||
first_name: billing_info[:first_name],
|
||||
last_name: billing_info[:last_name],
|
||||
email: params[:email],
|
||||
password: params[:password],
|
||||
password_confirmation: params[:password],
|
||||
terms_of_service: terms_of_service,
|
||||
instruments: [{:instrument_id => 'other', :proficiency_level => 1, :priority => 1}],
|
||||
birth_date: nil,
|
||||
location: {:country => billing_info[:country], :state => billing_info[:state], :city => billing_info[:city]},
|
||||
musician: true,
|
||||
skip_recaptcha: true,
|
||||
invited_user: nil,
|
||||
fb_signup: nil,
|
||||
signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm",
|
||||
any_user: any_user,
|
||||
reuse_card: reuse_card_next_time
|
||||
}
|
||||
|
||||
user = UserManager.new.signup(options)
|
||||
|
|
@ -61,9 +61,9 @@ class ApiRecurlyController < ApiController
|
|||
@account = @client.find_or_create_account(current_user, billing_info)
|
||||
end
|
||||
|
||||
render :json=>account_json(@account)
|
||||
render :json => account_json(@account)
|
||||
rescue RecurlyClientError => x
|
||||
render json: { :message => x.inspect, errors: x.errors }, :status => 404
|
||||
render json: {:message => x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -71,86 +71,80 @@ class ApiRecurlyController < ApiController
|
|||
@client.delete_account(current_user)
|
||||
render json: {}, status: 200
|
||||
rescue RecurlyClientError => x
|
||||
render json: { :message => x.inspect, errors: x.errors}, :status => 404
|
||||
render json: {:message => x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
# get Recurly account
|
||||
def get_account
|
||||
@account = @client.get_account(current_user)
|
||||
|
||||
render :json=>account_json(@account)
|
||||
render :json => account_json(@account)
|
||||
rescue RecurlyClientError => e
|
||||
render json: { message: x.inspect, errors: x.errors}, :status => 404
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
# get Recurly payment history
|
||||
def payment_history
|
||||
@payments=@client.payment_history(current_user)
|
||||
render :json=>{payments: @payments}
|
||||
render :json => {payments: @payments}
|
||||
rescue RecurlyClientError => x
|
||||
render json: { message: x.inspect, errors: x.errors}, :status => 404
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
# update Recurly account
|
||||
def update_account
|
||||
@account=@client.update_account(current_user, params[:billing_info])
|
||||
render :json=>account_json(@account)
|
||||
rescue RecurlyClientError => x
|
||||
render json: { message: x.inspect, errors: x.errors}, :status => 404
|
||||
@account=@client.update_account(current_user, params[:billing_info])
|
||||
render :json => account_json(@account)
|
||||
rescue RecurlyClientError => x
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
# get Billing Information
|
||||
def billing_info
|
||||
@account = @client.get_account(current_user)
|
||||
if @account
|
||||
render :json=> account_json(@account)
|
||||
render :json => account_json(@account)
|
||||
else
|
||||
render :json=> {}, :status => 404
|
||||
render :json => {}, :status => 404
|
||||
end
|
||||
rescue RecurlyClientError => x
|
||||
render json: { message: x.inspect, errors: x.errors}, :status => 404
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
# update Billing Information
|
||||
def update_billing_info
|
||||
@account=@client.update_billing_info(current_user, params[:billing_info])
|
||||
render :json=> account_json(@account)
|
||||
@account = @client.update_billing_info(current_user, params[:billing_info])
|
||||
render :json => account_json(@account)
|
||||
rescue RecurlyClientError => x
|
||||
render json: { message: x.inspect, errors: x.errors}, :status => 404
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
def place_order
|
||||
error=nil
|
||||
response = {jam_tracks:[]}
|
||||
response = {jam_tracks: []}
|
||||
|
||||
sale = Sale.create(current_user)
|
||||
sales = Sale.place_order(current_user, current_user.shopping_carts)
|
||||
|
||||
if sale.valid?
|
||||
current_user.shopping_carts.each do |shopping_cart|
|
||||
jam_track = shopping_cart.cart_product
|
||||
|
||||
# if shopping_cart has any marked_for_redeem, then we zero out the price by passing in 'free'
|
||||
# NOTE: shopping_carts have the idea of quantity, but you should only be able to buy at most one JamTrack. So anything > 0 is considered free for a JamTrack
|
||||
|
||||
jam_track_right = @client.place_order(current_user, jam_track, shopping_cart, sale)
|
||||
# build up the response object with JamTracks that were purchased.
|
||||
# if this gets more complicated, we should switch to RABL
|
||||
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
|
||||
sales.each do |sale|
|
||||
if sale.is_jam_track_sale?
|
||||
sale.sale_line_items.each do |line_item|
|
||||
jam_track = line_item.product
|
||||
jam_track_right = jam_track.right_for_user(current_user)
|
||||
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
|
||||
end
|
||||
end
|
||||
else
|
||||
error = 'can not create sale'
|
||||
end
|
||||
|
||||
if error
|
||||
render json: { errors: {message:error}}, :status => 404
|
||||
render json: {errors: {message: error}}, :status => 404
|
||||
else
|
||||
render :json=>response, :status=>200
|
||||
render :json => response, :status => 200
|
||||
end
|
||||
rescue RecurlyClientError => x
|
||||
render json: { message: x.inspect, errors: x.errors}, :status => 404
|
||||
render json: {message: x.inspect, errors: x.errors}, :status => 404
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def create_client
|
||||
@client = RecurlyClient.new
|
||||
end
|
||||
|
|
@ -173,5 +167,5 @@ private
|
|||
billing_info: billing_info
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
end # class
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
class ApiSalesController < ApiController
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
data = Sale.index(current_user,
|
||||
page: params[:page],
|
||||
per_page: params[:per_page])
|
||||
|
||||
|
||||
@sales = data[:query]
|
||||
@next = data[:next_page]
|
||||
render "api_sales/index", :layout => nil
|
||||
end
|
||||
end
|
||||
|
|
@ -46,6 +46,7 @@ class ApiUsersController < ApiController
|
|||
@user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments)
|
||||
@user.update_genres(params[:genres].nil? ? [] : params[:genres]) if params.has_key?(:genres)
|
||||
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
|
||||
@user.show_whats_next_count = params[:show_whats_next_count] if params.has_key?(:show_whats_next_count)
|
||||
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
|
||||
@user.biography = params[:biography] if params.has_key?(:biography)
|
||||
@user.mod_merge(params[:mods]) if params[:mods]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class ClientsController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
gon.recurly_tax_estimate_jam_track_plan = Rails.application.config.recurly_tax_estimate_jam_track_plan
|
||||
render :layout => 'client'
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class LandingsController < ApplicationController
|
|||
jam_track = JamTrack.first
|
||||
end
|
||||
|
||||
gon.jam_track_plan_code = jam_track.plan_code
|
||||
gon.jam_track_plan_code = jam_track.plan_code if jam_track
|
||||
render 'product_jamtracks', layout: 'web'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -215,7 +215,24 @@ class UsersController < ApplicationController
|
|||
#@jamfest_2014 = Event.find_by_id('a2dfbd26-9b17-4446-8c61-b67a542ea6ee') unless @jamfest_2014 # development ID
|
||||
# temporary--end
|
||||
|
||||
@welcome_page = true
|
||||
#@welcome_page = true
|
||||
render :layout => "web"
|
||||
end
|
||||
|
||||
# DO NOT USE CURRENT_USER IN THIS ROUTINE. IT'S CACHED FOR THE WHOLE SITE
|
||||
def home
|
||||
|
||||
@no_user_dropdown = true
|
||||
@promo_buzz = PromoBuzz.active
|
||||
|
||||
if Rails.application.config.use_promos_on_homepage
|
||||
@promo_latest = PromoLatest.active
|
||||
else
|
||||
@promo_latest, start = Feed.index(nil, limit: 10)
|
||||
end
|
||||
|
||||
|
||||
gon.signed_in = !current_user.nil?
|
||||
render :layout => "web"
|
||||
end
|
||||
|
||||
|
|
@ -396,6 +413,19 @@ JS
|
|||
end
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
unless @user = User.read_access_token(params[:user_token])
|
||||
redirect_to '/'
|
||||
end if params[:user_token].present?
|
||||
|
||||
if request.get?
|
||||
|
||||
elsif request.post?
|
||||
@user.subscribe_email = false
|
||||
@user.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def is_native_client
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
node :next_page do |page|
|
||||
@next
|
||||
end
|
||||
|
||||
node :entries do |page|
|
||||
partial "api_sales/show", object: @sales
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
object @sale
|
||||
|
||||
attributes :id, :recurly_invoice_id, :recurly_subtotal_in_cents, :recurly_tax_in_cents, :recurly_total_in_cents, :recurly_currency, :sale_type, :recurly_invoice_number, :state, :created_at
|
||||
|
||||
child(:recurly_transactions => :recurly_transactions) {
|
||||
attributes :transaction_type, :amount_in_cents
|
||||
}
|
||||
|
||||
child(:sale_line_items => :line_items) {
|
||||
attributes :id, :product_info
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
object @user
|
||||
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :reuse_card, :purchased_jamtracks_count
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count
|
||||
|
||||
if @user.musician?
|
||||
node :location do @user.location end
|
||||
|
|
@ -10,7 +10,7 @@ end
|
|||
|
||||
# give back more info if the user being fetched is yourself
|
||||
if @user == current_user
|
||||
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter, :new_notifications
|
||||
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count
|
||||
|
||||
node :geoiplocation do |user|
|
||||
geoiplocation = current_user.geoiplocation
|
||||
|
|
|
|||
|
|
@ -118,7 +118,13 @@
|
|||
</div>
|
||||
|
||||
<div class="account-mid payments">
|
||||
<a id="account-payment-history-link" href="#">View Payment History</a>
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
.screen.secondary layout="screen" layout-id="account/paymentHistory" class="screen secondary" id="account-payment-history"
|
||||
|
||||
.content
|
||||
.content-head
|
||||
.content-icon=image_tag("content/icon_account.png", height:20, width:27 )
|
||||
h1 my account
|
||||
=render "screen_navigation"
|
||||
.content-body
|
||||
.content-body-scroller
|
||||
|
||||
.account-left
|
||||
h2 payment history:
|
||||
table.payment-table
|
||||
thead
|
||||
tr
|
||||
th DATE
|
||||
th METHOD
|
||||
th DESCRIPTION
|
||||
th STATUS
|
||||
th AMOUNT
|
||||
tbody
|
||||
a.btn-next-pager href="/api/sales?page=1" Next
|
||||
.end-of-payments-list.end-of-list="No more payment history"
|
||||
|
||||
|
||||
.input-aligner
|
||||
a.back href="" class="button-grey" BACK
|
||||
br clear="all"
|
||||
|
||||
|
||||
|
||||
|
||||
script#template-payment-history-row type="text/template"
|
||||
tr
|
||||
td
|
||||
| {{data.date}}
|
||||
td.capitalize
|
||||
| {{data.payment_method}}
|
||||
td
|
||||
| {{data.description}}
|
||||
td.capitalize
|
||||
| {{data.status}}
|
||||
td.amount class="{{data.status}}"
|
||||
| ${{data.amount}}
|
||||
|
|
@ -115,10 +115,8 @@ script type='text/template' id='template-order-content'
|
|||
.order-right-page
|
||||
h2 PLACE ORDER
|
||||
.recurly-data.hidden
|
||||
= "{% _.each(data.carts, function(cart) { %}"
|
||||
.plan data-plan-code="{{cart.product_info.plan_code}}"
|
||||
input data-recurly="plan" type="text" value="{{cart.product_info.plan_code}}"
|
||||
= "{% }); %}"
|
||||
.plan.jamtrack data-plan-code="{{gon.recurly_tax_estimate_jam_track_plan}}"
|
||||
input data-recurly="plan" type="text" value="{{gon.recurly_tax_estimate_jam_track_plan}}"
|
||||
.order-summary
|
||||
.place-order-center
|
||||
a.button-orange.place-order href="#" PLACE YOUR ORDER
|
||||
|
|
|
|||
|
|
@ -30,6 +30,29 @@ script type="text/template" id="template-help-can-move-on"
|
|||
script type="text/template" id="template-help-tweak-asio-settings"
|
||||
| Click here to try faster ASIO settings.
|
||||
|
||||
script type="text/template" id="template-help-high-latency-notice"
|
||||
.help-high-latency-notice
|
||||
| {% if(data.additional == 'asio') { %}
|
||||
p.gear-specific-latency-notice Tip: click the ASIO SETTINGS button to try faster ASIO settings.
|
||||
p
|
||||
| If you are unable to get your audio gear latency below 20 milliseconds, you can click NEXT to proceed through setup with a high-latency audio profile. This will allow you to play with JamTracks and backing tracks, but not play with others.
|
||||
p
|
||||
a href="https://jamkazam.desk.com/customer/portal/articles/1520627-my-audio-gear-won-t-pass-latency-or-i-o-tests" rel="external" Click here
|
||||
| for more troubleshooting tips to speed up your audio gear setup.
|
||||
| {% } else if(data.additional == 'macosx-builtin') { %}
|
||||
p.gear-specific-latency-notice Tip: Insert your headphones on a Mac to bring your latency down, and click the RESYNC button to try again.
|
||||
p
|
||||
| If you are unable to get your audio gear latency below 20 milliseconds, you can click NEXT to proceed through setup with a high-latency audio profile. This will allow you to play with JamTracks and backing tracks, but not play with others.
|
||||
p
|
||||
a href="https://jamkazam.desk.com/customer/portal/articles/1520627-my-audio-gear-won-t-pass-latency-or-i-o-tests" rel="external" Click here
|
||||
| for more troubleshooting tips to speed up your audio gear setup.
|
||||
| {% } else { %}
|
||||
p.general-info
|
||||
| Your computer and interface are processing audio too slowly to play online in real-time sessions with other musicians over the Internet. You may click NEXT to proceed through setup to play alone in sessions with JamTracks or backing tracks, or if you want to improve your speed score to play online,
|
||||
a href="https://jamkazam.desk.com/customer/portal/articles/1520627-my-audio-gear-won-t-pass-latency-or-i-o-tests" rel="external" click here
|
||||
| for a troubleshooting article.
|
||||
| {% } %}
|
||||
|
||||
script type="text/template" id="template-help-session-plus-musicians"
|
||||
| Plus any interested JamKazam musicians that I approve.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.network-test
|
||||
.help-text In this step, you will test your router and Internet connection to ensure that you can play in online sessions, and to see how many musicians can be in a session with you based on your internet connection.
|
||||
.help-text In this step, you will test your router and Internet connection to ensure that you can play in online sessions, and to see how many musicians can be in a session with you based on your internet connection. If you don't want to play online in real-time sessions, you can click NEXT to skip this step.
|
||||
.wizard-step-content
|
||||
.wizard-step-column
|
||||
%h2 Instructions
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<!-- recording play controls -->
|
||||
<div class="recording recording-controls has-mix">
|
||||
|
||||
<div class="jam-track-get-ready">
|
||||
<div class="spinner-small"></div>
|
||||
<span>Get Ready!</span>
|
||||
</div>
|
||||
<!-- play button -->
|
||||
<a class="left play-button" href="#">
|
||||
<%= image_tag "content/icon_playbutton.png", {:height => 20, :width => 20, :class=> "playbutton"} %>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@
|
|||
span
|
||||
| Settings
|
||||
.session-tracks-scroller
|
||||
#session-mytracks-notracks
|
||||
p.notice
|
||||
| You have not set up any inputs for your instrument or vocals.
|
||||
| If you want to hear yourself play through the JamKazam app,
|
||||
| and let the app mix your live playing with JamTracks, or with other musicians in online sessions,
|
||||
a.open-ftue-no-tracks href='#' click here now.
|
||||
#session-mytracks-container
|
||||
#voice-chat.voicechat[style="display:none;" mixer-id=""]
|
||||
.voicechat-label
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
<%= render "account_jamtracks" %>
|
||||
<%= render "account_session_detail" %>
|
||||
<%= render "account_session_properties" %>
|
||||
<%= render "account_payment_history" %>
|
||||
<%= render "inviteMusicians" %>
|
||||
<%= render "hoverBand" %>
|
||||
<%= render "hoverFan" %>
|
||||
|
|
@ -214,6 +215,9 @@
|
|||
var accountAudioProfile = new JK.AccountAudioProfile(JK.app);
|
||||
accountAudioProfile.initialize();
|
||||
|
||||
var accountPaymentHistoryScreen = new JK.AccountPaymentHistoryScreen(JK.app);
|
||||
accountPaymentHistoryScreen.initialize();
|
||||
|
||||
var searchResultScreen = new JK.SearchResultScreen(JK.app);
|
||||
searchResultScreen.initialize();
|
||||
|
||||
|
|
@ -249,8 +253,6 @@
|
|||
var jamtrackScreen = new JK.JamTrackScreen(JK.app);
|
||||
jamtrackScreen.initialize();
|
||||
|
||||
|
||||
|
||||
var jamtrackLanding = new JK.JamTrackLanding(JK.app);
|
||||
jamtrackLanding.initialize();
|
||||
|
||||
|
|
@ -299,6 +301,9 @@
|
|||
var allSyncsDialog = new JK.AllSyncsDialog(JK.app);
|
||||
allSyncsDialog.initialize();
|
||||
|
||||
var singlePlayerProfileGuardDialog = new JK.SinglePlayerProfileGuardDialog(JK.app);
|
||||
singlePlayerProfileGuardDialog.initialize();
|
||||
|
||||
// do a client update early check upon initialization
|
||||
JK.ClientUpdateInstance.check()
|
||||
|
||||
|
|
@ -312,6 +317,9 @@
|
|||
var jamServer = new JK.JamServer(JK.app, function(event_type) {JK.app.activeElementEvent(event_type)});
|
||||
jamServer.initialize();
|
||||
|
||||
var clientInit = new JK.ClientInit();
|
||||
clientInit.init();
|
||||
|
||||
// latency_tester does not want to be here - redirect it
|
||||
if(window.jamClient.getOperatingMode && window.jamClient.getOperatingMode() == "server") {
|
||||
window.location = "/latency_tester";
|
||||
|
|
|
|||
|
|
@ -37,3 +37,4 @@
|
|||
= render 'dialogs/openBackingTrackDialog'
|
||||
= render 'dialogs/loginRequiredDialog'
|
||||
= render 'dialogs/jamtrackPaymentHistoryDialog'
|
||||
= render 'dialogs/singlePlayerProfileGuard'
|
||||
|
|
@ -36,16 +36,24 @@
|
|||
a href="#" class="google-invite"
|
||||
= image_tag "content/icon_google.png", {:align=>"absmiddle", :height => 26, :width => 26 }
|
||||
span Google+
|
||||
.column.get-a-free-jamtrack-section
|
||||
h2.get-a-free-jamtrack.hidden GET A FREE JAMTRACK
|
||||
h2.browse-jamtracks.hidden CHECK OUT JAMTRACKS
|
||||
.blurb
|
||||
| JamTracks are the best way to play with your favorite music. Unlike traditional backing tracks,
|
||||
| they are complete multitrack recordings, with fully isolated tracks for each part. 
|
||||
span.jamtracks-limited-time.hidden For a limited time, you can get your first JamTrack free.
|
||||
| Check it out!
|
||||
.action-button
|
||||
a.button-orange.browse-jamtrack rel="external" href="#" LEARN MORE
|
||||
br clear="both"
|
||||
.row.find-connect
|
||||
.column
|
||||
h2 CREATE A "REAL" SESSION
|
||||
.blurb
|
||||
| You can create a session to start immediately and hope others join, but this doesn’t work well. It’s better
|
||||
to schedule a session and invite friends or the community to join you. Watch a video to learn how, then
|
||||
schedule your first session!
|
||||
| You can create sessions that start immediately and see who joins, or you can schedule sessions, invite friends, and others from the community, and manage RSVPs. Learn how.
|
||||
.action-button
|
||||
a.button-orange rel="external" href="https://www.youtube.com/watch?v=EZZuGcDUoWk" WATCH VIDEO
|
||||
br clear="both"
|
||||
.row.find-connect
|
||||
.column
|
||||
h2 FIND SESSIONS TO JOIN
|
||||
.blurb
|
||||
|
|
@ -53,14 +61,6 @@
|
|||
to learn about how to find and select good sessions to join.
|
||||
.action-button
|
||||
a.button-orange.setup-gear rel="external" href="https://www.youtube.com/watch?v=xWponSJo-GU" WATCH VIDEO
|
||||
|
||||
.column
|
||||
h2 CONNECT WITH MUSICIANS
|
||||
.blurb
|
||||
| To play more music, tap into our growing
|
||||
community to connect with other musicians. Watch this video for tips on how to do this.
|
||||
.action-button
|
||||
a.button-orange rel="external" href="https://www.youtube.com/watch?v=4KWklSZZxRc" WATCH VIDEO
|
||||
br clear="both"
|
||||
.row.full.learn-more
|
||||
.column
|
||||
|
|
|
|||
|
|
@ -18,16 +18,3 @@
|
|||
.jamtrack_buttons
|
||||
.right
|
||||
a.button-orange class='btnCancel' layout-action='cancel' OK
|
||||
|
||||
script#template-payment-history-row type="text/template"
|
||||
tr
|
||||
td
|
||||
| {{data.date}}
|
||||
td
|
||||
| ${{data.amount}}
|
||||
td.capitalize
|
||||
| {{data.status}}
|
||||
td.capitalize
|
||||
| {{data.payment_method}}
|
||||
td
|
||||
| {{data.reference}}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
.dialog.dialog-overlay-sm layout='dialog' layout-id='single-player-profile-dialog' id='single-player-profile-dialog'
|
||||
.content-head
|
||||
= image_tag "content/icon_alert.png", {:width => 24, :height => 24, :class => 'content-icon' }
|
||||
h1 Application Notice
|
||||
.dialog-inner
|
||||
|
||||
p.high-latency.hidden
|
||||
| Your audio profile has a latency score of
|
||||
span.audio-latency
|
||||
br
|
||||
br
|
||||
| This is too high to play with others in real-time. However, you can play with JamTracks and backing tracks by yourself in a private session, or go to the gear setup wizard and add a new audio profile with lower latency.
|
||||
|
||||
p.has-no-inputs.hidden
|
||||
| You are currently using the default system profile, which has no audio inputs.
|
||||
br
|
||||
br
|
||||
| With this profile, you can't play with others in real-time. However, you can play with JamTracks and backing tracks by yourself in a private session, or go to the gear setup wizard and add a new audio profile that uses your gear.
|
||||
.right.action-buttons
|
||||
a.button-grey.btn-cancel href='#' layout-action="cancel" CANCEL
|
||||
a.button-grey.btn-gear-setup href="/client#/account/audio" GO TO GEAR SETUP
|
||||
a.button-orange.btn-private-session href="#" PRIVATE SESSION
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
- provide(:page_name, 'home')
|
||||
|
||||
.home-column
|
||||
= link_to image_tag("web/thumbnail_jamtracks.jpg", :alt => "JamTracks explanatory video"), '#', class: "jamtracks-video video-item", 'data-video-header' => 'JamTracks', 'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'
|
||||
h3 Complete, Multi-Track Backing Tracks
|
||||
|
||||
p
|
||||
strong JamTracks
|
||||
| are the best way to play with your favorite music. Unlike traditional backing tracks, JamTracks are complete multitrack recordings, with fully isolated tracks for each part.
|
||||
|
||||
= link_to image_tag("web/button_cta_jamtrack.png", width: 234, height:57), '/client#/jamtrack', class: 'cta-button jamtracks'
|
||||
br clear="all"
|
||||
.extra-links
|
||||
.learn-more
|
||||
a.learn-more-jamtracks href='/products/jamtracks' learn more
|
||||
|
||||
.home-column
|
||||
= link_to image_tag("web/thumbnail_platform.jpg", :alt => "JamKazam explanatory video!"), '#', class: "platform-video video-item", 'data-video-header' => 'JamKazam Platform', 'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'
|
||||
h3 Online Music Collaboration Platform
|
||||
|
||||
p
|
||||
strong JamKazam
|
||||
| is an innovative live music platform and social network, enabling musicians to play music together in real time from different locations over the Internet as if they are sitting in the same room.
|
||||
|
||||
= link_to image_tag("web/button_cta_platform.png", width: 234, height: 57), '/signup', class: 'cta-button platform'
|
||||
.extra-links
|
||||
span.learn-more.shared
|
||||
a.learn-more-platform href='/products/platform' learn more
|
||||
span.sign-in-holder.shared
|
||||
a.sign-in href='/signin' sign in
|
||||
|
||||
br clear="all"
|
||||
|
||||
.home-column.last
|
||||
= link_to image_tag("web/thumbnail_jamblaster.jpg", :alt => "JamBlaster explanatory video!"), '#', class: "jamblaster-video video-item", 'data-video-header' => 'JamBlaster', 'data-video-url' => 'http://www.youtube.com/embed/gAJAIHMyois?autoplay=1'
|
||||
h3 Ultra Low-Latency Audio Interface
|
||||
|
||||
p
|
||||
| The
|
||||
strong JamBlaster
|
||||
| is a device designed from the ground up to meet the requirements of online music play, vastly extending the range over which musicians can play together across the Internet.
|
||||
|
||||
= link_to image_tag("web/button_cta_jamblaster.png", width: 234, height: 57), '/products/jamblaster', class: 'cta-button jamblaster'
|
||||
.extra-links
|
||||
.learn-more
|
||||
a.learn-more-jamblaster href='/products/jamblaster' learn more
|
||||
br clear="all"
|
||||
|
||||
br clear="all"
|
||||
|
||||
- content_for :after_black_bar do
|
||||
.latest-promo
|
||||
= render :partial => "latest"
|
||||
.endorsement-promo
|
||||
.home-buzz
|
||||
h2 What Musicians in the JamKazam Community Are Saying
|
||||
= link_to image_tag("web/thumbnail_buzz.jpg", :alt => "JamKazam Endorsements!", width:300), '#', class: "endorsements-video video-item", 'data-video-header' => 'JamKazam Community', 'data-video-url' => 'http://www.youtube.com/embed/_7qj5RXyHCo?autoplay=1'
|
||||
|
||||
|
||||
br clear="all"
|
||||
|
||||
javascript:
|
||||
window.JK.HomePage();
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
= provide(:title, 'Unsubscribe')
|
||||
|
||||
- if request.get?
|
||||
%h2 Unsubscribe from all JamKazam email for address #{@user} ?
|
||||
%br
|
||||
= form_tag("") do
|
||||
= submit_tag('Unsubscribe')
|
||||
= hidden_field_tag(:user_token, params[:user_token])
|
||||
- elsif request.post?
|
||||
- if @user && ! @user.subscribe_email
|
||||
%h2 You have been unsubscribed.
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ if defined?(Bundler)
|
|||
# amount of time before we think the queue is stuck
|
||||
config.signing_job_queue_max_time = 20 # 20 seconds
|
||||
|
||||
config.email_alerts_alias = 'nobody@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
|
||||
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
|
||||
config.email_generic_from = 'nobody@jamkazam.com'
|
||||
config.email_smtp_address = 'smtp.sendgrid.net'
|
||||
config.email_smtp_port = 587
|
||||
|
|
@ -323,5 +323,6 @@ if defined?(Bundler)
|
|||
config.one_free_jamtrack_per_user = true
|
||||
|
||||
config.nominated_jam_track = 'jamtrack-pearljam-alive'
|
||||
config.recurly_tax_estimate_jam_track_plan = 'jamtrack-acdc-backinblack'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ SampleApp::Application.routes.draw do
|
|||
resources :users
|
||||
resources :sessions, only: [:new, :create, :destroy]
|
||||
|
||||
root to: 'users#welcome'
|
||||
root to: 'users#home'
|
||||
|
||||
# signup, and signup completed, related pages
|
||||
match '/signup', to: 'users#new', :via => 'get'
|
||||
|
|
@ -79,6 +79,8 @@ SampleApp::Application.routes.draw do
|
|||
match '/reset_password_token' => 'users#reset_password_token', :via => :get
|
||||
match '/reset_password_complete' => 'users#reset_password_complete', :via => :post
|
||||
|
||||
match '/unsubscribe/:user_token' => 'users#unsubscribe', via: [:get, :post]
|
||||
|
||||
# email update
|
||||
match '/confirm_email' => 'users#finalize_update_email', :as => 'confirm_email' # NOTE: if you change this, you break outstanding email changes because links in user inboxes are broken
|
||||
|
||||
|
|
@ -276,6 +278,9 @@ SampleApp::Application.routes.draw do
|
|||
match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put
|
||||
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
|
||||
|
||||
# sale info
|
||||
match '/sales' => 'api_sales#index', :via => :get
|
||||
|
||||
# login/logout
|
||||
match '/auth_session' => 'api_users#auth_session_create', :via => :post
|
||||
match '/auth_session' => 'api_users#auth_session_delete', :via => :delete
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiSalesController do
|
||||
render_views
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:jam_track) {FactoryGirl.create(:jam_track)}
|
||||
|
||||
before(:each) do
|
||||
controller.current_user = user
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
|
||||
it "empty" do
|
||||
get :index, { :format => 'json'}
|
||||
|
||||
response.should be_success
|
||||
body = JSON.parse(response.body)
|
||||
body['next_page'].should be_nil
|
||||
body['entries'].should eq([])
|
||||
end
|
||||
|
||||
it "one item" do
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
sale.recurly_invoice_id = SecureRandom.uuid
|
||||
sale.save!
|
||||
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
|
||||
|
||||
get :index, { :format => 'json'}
|
||||
response.should be_success
|
||||
body = JSON.parse(response.body)
|
||||
body['next_page'].should be_nil
|
||||
entries = body['entries']
|
||||
entries.should have(1).items
|
||||
sale_entry = entries[0]
|
||||
sale_entry["line_items"].should have(1).items
|
||||
sale_entry["recurly_transactions"].should have(0).items
|
||||
|
||||
|
||||
transaction = FactoryGirl.create(:recurly_transaction_web_hook, invoice_id: sale.recurly_invoice_id, transaction_type: RecurlyTransactionWebHook::VOID)
|
||||
|
||||
get :index, { :format => 'json'}
|
||||
response.should be_success
|
||||
body = JSON.parse(response.body)
|
||||
body['next_page'].should be_nil
|
||||
entries = body['entries']
|
||||
entries.should have(1).items
|
||||
sale_entry = entries[0]
|
||||
sale_entry["line_items"].should have(1).items
|
||||
sale_entry["recurly_transactions"].should have(1).items
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -756,4 +756,25 @@ FactoryGirl.define do
|
|||
bpm 120
|
||||
tap_in_count 3
|
||||
end
|
||||
|
||||
factory :recurly_transaction_web_hook, :class => JamRuby::RecurlyTransactionWebHook do
|
||||
|
||||
transaction_type JamRuby::RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT
|
||||
sequence(:recurly_transaction_id ) { |n| "recurly-transaction-id-#{n}" }
|
||||
sequence(:subscription_id ) { |n| "subscription-id-#{n}" }
|
||||
sequence(:invoice_id ) { |n| "invoice-id-#{n}" }
|
||||
sequence(:invoice_number ) { |n| 1000 + n }
|
||||
invoice_number_prefix nil
|
||||
action 'purchase'
|
||||
status 'success'
|
||||
transaction_at Time.now
|
||||
amount_in_cents 199
|
||||
reference 100000
|
||||
message 'meh'
|
||||
association :user, factory: :user
|
||||
|
||||
factory :recurly_transaction_web_hook_failed do
|
||||
transaction_type JamRuby::RecurlyTransactionWebHook::FAILED_PAYMENT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
|
|||
subject { page }
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:jam_track) {FactoryGirl.create(:jam_track)}
|
||||
|
||||
before(:each) do
|
||||
UserMailer.deliveries.clear
|
||||
|
|
@ -135,6 +136,26 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
|
|||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "payment history" do
|
||||
|
||||
it "show 1 sale" do
|
||||
|
||||
sale = Sale.create_jam_track_sale(user)
|
||||
shopping_cart = ShoppingCart.create(user, jam_track)
|
||||
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
|
||||
|
||||
visit "/client#/account"
|
||||
|
||||
find('.account-mid.payments', text: 'You have made 1 purchase.')
|
||||
|
||||
find("#account-payment-history-link").trigger(:click)
|
||||
find('h2', text: 'payment history:')
|
||||
find('table tr td', text: '$0.00') # 1st purchase is free
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
|
|||
|
||||
# make sure plans are there
|
||||
@recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack)
|
||||
@recurlyClient.create_jam_track_plan(@jamtrack_pearljam_evenflow) unless @recurlyClient.find_jam_track_plan(@jamtrack_pearljam_evenflow)
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -552,7 +550,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
|
|||
sale = user.sales.first
|
||||
sale.sale_line_items.length.should eq(2)
|
||||
|
||||
acdc_sale = SaleLineItem.find_by_recurly_subscription_uuid(acdc.recurly_subscription_uuid)
|
||||
acdc_sale = SaleLineItem.find_by_recurly_adjustment_uuid(acdc.recurly_adjustment_uuid)
|
||||
acdc_sale.recurly_plan_code.should eq(jamtrack_acdc_backinblack.plan_code)
|
||||
acdc_sale.product_type.should eq('JamTrack')
|
||||
acdc_sale.product_id.should eq(jamtrack_acdc_backinblack.id)
|
||||
|
|
@ -560,7 +558,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
|
|||
acdc_sale.free.should eq(0)
|
||||
acdc_sale.unit_price.should eq(1.99)
|
||||
acdc_sale.sale.should eq(sale)
|
||||
pearljam_sale = SaleLineItem.find_by_recurly_subscription_uuid(pearljam.recurly_subscription_uuid)
|
||||
pearljam_sale = SaleLineItem.find_by_recurly_adjustment_uuid(pearljam.recurly_adjustment_uuid)
|
||||
pearljam_sale.recurly_plan_code.should eq(jamtrack_pearljam_evenflow.plan_code)
|
||||
pearljam_sale.product_type.should eq('JamTrack')
|
||||
pearljam_sale.product_id.should eq(jamtrack_pearljam_evenflow.id)
|
||||
|
|
@ -660,7 +658,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
|
|||
guy.sales.length.should eq(1)
|
||||
sale = guy.sales.first
|
||||
sale.sale_line_items.length.should eq(1)
|
||||
acdc_sale = SaleLineItem.find_by_recurly_subscription_uuid(jam_track_right.recurly_subscription_uuid)
|
||||
acdc_sale = SaleLineItem.find_by_recurly_adjustment_uuid(jam_track_right.recurly_adjustment_uuid)
|
||||
acdc_sale.recurly_plan_code.should eq(jamtrack_acdc_backinblack.plan_code)
|
||||
acdc_sale.product_type.should eq('JamTrack')
|
||||
acdc_sale.product_id.should eq(jamtrack_acdc_backinblack.id)
|
||||
|
|
@ -710,7 +708,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
|
|||
guy.sales.length.should eq(2)
|
||||
sale = guy.sales.last
|
||||
sale.sale_line_items.length.should eq(1)
|
||||
acdc_sale = SaleLineItem.find_by_recurly_subscription_uuid(jam_track_right.recurly_subscription_uuid)
|
||||
acdc_sale = SaleLineItem.find_by_recurly_adjustment_uuid(jam_track_right.recurly_adjustment_uuid)
|
||||
acdc_sale.recurly_plan_code.should eq(jamtrack_pearljam_evenflow.plan_code)
|
||||
acdc_sale.product_type.should eq('JamTrack')
|
||||
acdc_sale.product_id.should eq(jamtrack_pearljam_evenflow.id)
|
||||
|
|
|
|||