From 0907c1acd1554d919891b5b79cc3ee9b5e09181d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 19 Feb 2015 01:06:50 -0600 Subject: [PATCH 1/8] VRFS-2785 : Factory recurly client into ruby project. Fix references and specs as appropriate. --- ruby/Gemfile | 1 + ruby/lib/jam_ruby.rb | 1 + ruby/lib/jam_ruby/init.rb | 17 ++++++++++++++++- .../lib => ruby/lib/jam_ruby}/recurly_client.rb | 2 ++ ruby/spec/factories.rb | 1 + .../spec/jam_ruby}/recurly_client_spec.rb | 2 +- web/app/controllers/api_recurly_controller.rb | 2 +- web/spec/controllers/api_recurly_spec.rb | 6 +----- 8 files changed, 24 insertions(+), 8 deletions(-) rename {web/lib => ruby/lib/jam_ruby}/recurly_client.rb (97%) rename {web/spec/managers => ruby/spec/jam_ruby}/recurly_client_spec.rb (99%) diff --git a/ruby/Gemfile b/ruby/Gemfile index 7a31165d1..8ef539d9a 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -49,6 +49,7 @@ gem 'iso-639' gem 'rubyzip' gem 'sanitize' gem 'influxdb', '0.1.8' +gem 'recurly' group :test do gem 'simplecov', '~> 0.7.1' diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index b94d7d9a2..e147eadc6 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -62,6 +62,7 @@ require "jam_ruby/resque/scheduled/stats_maker" require "jam_ruby/resque/google_analytics_event" require "jam_ruby/resque/batch_email_job" require "jam_ruby/mq_router" +require "jam_ruby/recurly_client" require "jam_ruby/base_manager" require "jam_ruby/connection_manager" require "jam_ruby/version" diff --git a/ruby/lib/jam_ruby/init.rb b/ruby/lib/jam_ruby/init.rb index adf32cf04..d74b80b5e 100644 --- a/ruby/lib/jam_ruby/init.rb +++ b/ruby/lib/jam_ruby/init.rb @@ -1,3 +1,18 @@ # initialize actionmailer ActionMailer::Base.raise_delivery_errors = true -ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__) \ No newline at end of file +ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__) + +# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more. +case JamRuby::Environment + when 'production' + Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6" + Recurly.subdomain = 'jamkazam' + when 'development' + Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6" + Recurly.subdomain = 'jamkazam-development' + else + Recurly.api_key = "4631527f203b41848523125b3ae51341" + Recurly.subdomain = 'jamkazam-test' +end + +Recurly.default_currency = 'USD' diff --git a/web/lib/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb similarity index 97% rename from web/lib/recurly_client.rb rename to ruby/lib/jam_ruby/recurly_client.rb index 4992814cb..419e3a377 100644 --- a/web/lib/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -8,9 +8,11 @@ module JamRuby options = account_hash(current_user, billing_info) account = nil 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 + puts "Error: #{x} : #{Kernel.caller}" raise RecurlyClientError, x.to_s else if account diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 42418a699..8c940bd0b 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -734,6 +734,7 @@ FactoryGirl.define do licensor_royalty_amount 0.999 pro_royalty_amount 0.999 available true + plan_code 'jamtrack-acdc-backinblack' genre JamRuby::Genre.first association :licensor, factory: :jam_track_licensor diff --git a/web/spec/managers/recurly_client_spec.rb b/ruby/spec/jam_ruby/recurly_client_spec.rb similarity index 99% rename from web/spec/managers/recurly_client_spec.rb rename to ruby/spec/jam_ruby/recurly_client_spec.rb index 8b1825cba..1cf58722f 100644 --- a/web/spec/managers/recurly_client_spec.rb +++ b/ruby/spec/jam_ruby/recurly_client_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require "recurly_client" +require "jam_ruby/recurly_client" describe RecurlyClient do let(:jamtrack) { FactoryGirl.create(:jam_track) } #let(:client) { RecurlyClient.new } diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 575366d55..cd0aa4b55 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -1,4 +1,4 @@ -require 'recurly_client' +require 'jam_ruby/recurly_client' class ApiRecurlyController < ApiController before_filter :api_signed_in_user before_filter :create_client diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index f2a79d552..fb0eeabfd 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -1,13 +1,9 @@ require 'spec_helper' -require 'recurly_client' -#require 'recurly/account' +require 'jam_ruby/recurly_client' describe ApiRecurlyController, :type=>:controller do render_views - # let(:user) { FactoryGirl.create(:user) } - # let(:jamtrack) { FactoryGirl.create(:jam_track) } - before(:each) do @user = FactoryGirl.create(:user) #@jamtrack = FactoryGirl.create(:jam_track) From bcd3785b4506b06e373a686542b31c45566ff4d9 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 19 Feb 2015 16:40:19 -0600 Subject: [PATCH 2/8] VRFS-2785 : Update recurly client with refund functionality. Unit test to verify. --- ruby/lib/jam_ruby/recurly_client.rb | 40 +++++++++++++++++++++++ ruby/spec/jam_ruby/recurly_client_spec.rb | 18 ++++++++++ 2 files changed, 58 insertions(+) diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index 419e3a377..eb70824c9 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -70,6 +70,46 @@ module JamRuby account end + def refund_user_subscription(current_user, jam_track) + jam_track_right=JamRuby::JamTrackRight.where("user_id=? AND jam_track_id=?", current_user.id, jam_track.id).first + if jam_track_right + refund_subscription(jam_track_right) + else + raise RecurlyClientError, "The user #{current_user} does not have a subscription to #{jam_track}" + end + end + + def refund_subscription(jam_track_right) + account = get_account(jam_track_right.user) + if (account.present?) + terminated = false + begin + jam_track = jam_track_right.jam_track + account.subscriptions.find_each do |subscription| + 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? + terminated = true + end + end + + if terminated + 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 + account + end + def place_order(current_user, jam_track) account = get_account(current_user) if (account.present?) diff --git a/ruby/spec/jam_ruby/recurly_client_spec.rb b/ruby/spec/jam_ruby/recurly_client_spec.rb index 1cf58722f..82217851d 100644 --- a/ruby/spec/jam_ruby/recurly_client_spec.rb +++ b/ruby/spec/jam_ruby/recurly_client_spec.rb @@ -98,6 +98,24 @@ describe RecurlyClient do @user.jam_track_rights.last.jam_track.id.should eq(@jamtrack.id) end + it "can refund subscription" do + @client.find_or_create_account(@user, @billing_info) + + # Place order: + expect{@client.place_order(@user, @jamtrack)}.not_to raise_error() + active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'} + @jamtrack.reload + @jamtrack.jam_track_rights.should have(1).items + + # Refund: + expect{@client.refund_user_subscription(@user, @jamtrack)}.not_to raise_error() + active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'} + active_subs.should have(0).items + + @jamtrack.reload + @jamtrack.jam_track_rights.should have(0).items + end + it "detects error on double order" do @client.find_or_create_account(@user, @billing_info) expect{@client.place_order(@user, @jamtrack)}.not_to raise_error() From e662c95fba9c33f2c3bb764054f3b312e27d7de1 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 19 Feb 2015 16:41:09 -0600 Subject: [PATCH 3/8] VRFS-2785 : Add Jam Track Right (purchased jam track) to admin ui. Hook up a refund action. --- admin/Gemfile | 1 + admin/app/admin/jam_track_right.rb | 87 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 admin/app/admin/jam_track_right.rb diff --git a/admin/Gemfile b/admin/Gemfile index 7502a288e..6f5c0b21e 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -74,6 +74,7 @@ gem 'sanitize' gem 'slim' gem 'influxdb', '0.1.8' gem 'influxdb-rails', '0.1.10' +gem 'recurly' group :libv8 do gem 'libv8', "~> 3.11.8" diff --git a/admin/app/admin/jam_track_right.rb b/admin/app/admin/jam_track_right.rb new file mode 100644 index 000000000..5e7c3687b --- /dev/null +++ b/admin/app/admin/jam_track_right.rb @@ -0,0 +1,87 @@ +require 'jam_ruby/recurly_client' +ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do + + menu :label => 'Purchased JamTracks', :parent => 'JamTracks' + + config.sort_order = 'updated_at DESC' + config.batch_actions = false + + #form :partial => 'form' + + index do + default_actions + + column "Order" do |right| + link_to("Place", order_admin_jam_track_right_path(right)) + " | " + + link_to("Refund", refund_admin_jam_track_right_path(right)) + end + + column "Last Name" do |right| + right.user.last_name + end + column "First Name" do |right| + right.user.first_name + end + column "Jam Track" do |right| + link_to(right.jam_track.name, admin_jam_track_right_path(right.jam_track)) + # right.jam_track + end + column "Plan Code" do |right| + + right.jam_track.plan_code + end + + + end + + form do |f| + f.inputs 'New Jam Track Right' do + f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false + f.input :user, :required=>true, collection: User.all, include_blank: false + end + f.actions + end + + member_action :order, :method => :get do + right = JamTrackRight.where("id=?",params[:id]).first + user = right.user + jam_track = right.jam_track + client = RecurlyClient.new + billing_info = { + first_name: user.first_name, + last_name: user.last_name, + address1: 'Test Address 1', + address2: 'Test Address 2', + city: user.city, + state: user.state, + country: user.country, + zip: '12345', + number: '4111-1111-1111-1111', + month: '08', + year: '2017', + verification_value: '111' + } + + begin + client.find_or_create_account(user, billing_info) + client.place_order(user, jam_track) + rescue RecurlyClientError=>x + redirect_to admin_jam_track_rights_path, notice: "Could not order #{jam_track} for #{user.to_s}: #{x.errors.inspect}" + else + redirect_to admin_jam_track_rights_path, notice: "Placed order of #{jam_track} for #{user.to_s}." + end + end + + member_action :refund, :method => :get do + right = JamTrackRight.where("id=?",params[:id]).first + client = RecurlyClient.new + + begin + client.refund_user_subscription(right.user, right.jam_track) + rescue RecurlyClientError=>x + redirect_to admin_jam_track_rights_path, notice: "Could not issue refund on #{right.jam_track} for #{right.user.to_s}: #{x.errors.inspect}" + else + redirect_to admin_jam_track_rights_path, notice: "Issued full refund on #{right.jam_track} for #{right.user.to_s}" + end + end +end \ No newline at end of file From 9d70400e91f350ff2d3892352cba196cdbd7fd4c Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 20 Feb 2015 09:50:03 -0600 Subject: [PATCH 4/8] * VRFS-2806 - disallow concurrent open of dialog --- web/app/assets/javascripts/session.js | 29 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 655fac730..91e5565d6 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -94,6 +94,7 @@ var $mixModeDropdown = null; var $templateMixerModeChange = null; var $closePlaybackRecording = null; + var $openBackingTrack = null; var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]; var rest = context.JK.Rest(); @@ -139,6 +140,8 @@ function afterShow(data) { + $openBackingTrack.removeClass('disabled'); + if(!context.JK.JamServer.connected) { promptLeave = false; app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.'); @@ -1925,6 +1928,13 @@ } function handleBackingTrackSelectedCallback(result) { + + $openBackingTrack.removeClass('disabled'); + + if(!sessionModel.inSession()) { + return; + } + if(result.success) { logger.debug("backing track selected: " + result.file); @@ -1937,8 +1947,6 @@ .fail(function(jqXHR) { app.notifyServerError(jqXHR, "Unable to Open BackingTrack For Playback"); }) - - } else { logger.debug("no backing track selected") @@ -2286,6 +2294,12 @@ } function openBackingTrack(e) { + + if($openBackingTrack.is('.disabled')) { + logger.debug("backing track dialog already open") + return false; + } + // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { app.notify({ @@ -2296,13 +2310,8 @@ return false; } + $openBackingTrack.addClass('disabled'); context.jamClient.ShowSelectBackingTrackDialog("window.JK.HandleBackingTrackSelectedCallback"); - - //app.layout.showDialog('open-backing-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { - // if(!data.cancel && data.result){ - // sessionModel.setBackingTrack(data.result); - // } - //}) return false; } @@ -2323,6 +2332,7 @@ } function openBackingTrackFile(e) { + // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { app.notify({ @@ -2611,7 +2621,7 @@ $('#recording-start-stop').on('click', startStopRecording); $('#open-a-recording').on('click', openRecording); $('#open-a-jamtrack').on('click', openJamTrack); - $('#open-a-backingtrack').on('click', openBackingTrack); + $openBackingTrack.on('click', openBackingTrack); $('#open-a-metronome').on('click', openMetronome); $('#session-invite-musicians').on('click', inviteMusicians); $('#session-invite-musicians2').on('click', inviteMusicians); @@ -2656,6 +2666,7 @@ $mixModeDropdown = $screen.find('select.monitor-mode') $templateMixerModeChange = $('#template-mixer-mode-change'); $closePlaybackRecording = $('#close-playback-recording') + $openBackingTrack = $('#open-a-backingtrack'); events(); From ea482da0dc1ddf4cc35a8816d3fb091f89fc8c88 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 20 Feb 2015 10:01:11 -0600 Subject: [PATCH 5/8] * VRFS-2808 - prevent duplicate attempt to open recording --- .../javascripts/dialog/localRecordingsDialog.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/web/app/assets/javascripts/dialog/localRecordingsDialog.js b/web/app/assets/javascripts/dialog/localRecordingsDialog.js index d08911d7c..10fdae9a1 100644 --- a/web/app/assets/javascripts/dialog/localRecordingsDialog.js +++ b/web/app/assets/javascripts/dialog/localRecordingsDialog.js @@ -7,6 +7,7 @@ var rest = context.JK.Rest(); var showing = false; var perPage = 10; + var openingRecording = false; function tbody() { return $('#local-recordings-dialog table.local-recordings tbody'); @@ -22,6 +23,7 @@ function beforeShow() { + openingRecording = false; emptyList(); resetPagination(); showing = true; @@ -89,6 +91,12 @@ function registerStaticEvents() { $('#local-recordings-dialog table.local-recordings tbody').on('click', 'tr', function(e) { + if(openingRecording) { + // prevent double-click spam + logger.debug("localRecordingDialog: ignoring duplicate open attempt") + return false; + } + var localState = $(this).attr('data-local-state'); if(localState == 'MISSING') { @@ -109,6 +117,8 @@ { var claimedRecording = $(this).data('server-model'); + openingRecording = true; + // tell the server we are about to start a recording rest.startPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id}) .done(function(response) { @@ -146,6 +156,9 @@ app.notifyServerError(jqXHR, "Unable to Open Recording For Playback"); }) + .always(function() { + openingRecording = false; + }) } From 40eea6319e1650c2d336040ed1f7605ad909d98b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 20 Feb 2015 10:40:23 -0600 Subject: [PATCH 6/8] Add recurly dependency. --- websocket-gateway/Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/websocket-gateway/Gemfile b/websocket-gateway/Gemfile index 3e32bbc4c..77ce57cc3 100644 --- a/websocket-gateway/Gemfile +++ b/websocket-gateway/Gemfile @@ -54,6 +54,7 @@ gem 'language_list' gem 'rubyzip' gem 'sanitize' gem 'influxdb', '0.1.8' +gem 'recurly' group :development do gem 'pry' From 00d8244c6a72632d7faf1a28eaf71a32571bca38 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 20 Feb 2015 16:40:23 -0600 Subject: [PATCH 7/8] VRFS-2786 : Change disabled text for jam track to say "Purchased". Update unit test as well. --- web/app/views/clients/_jamtrack.html.haml | 2 +- web/spec/features/jamtrack_shopping_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/views/clients/_jamtrack.html.haml b/web/app/views/clients/_jamtrack.html.haml index a45e932fe..2573197b3 100644 --- a/web/app/views/clients/_jamtrack.html.haml +++ b/web/app/views/clients/_jamtrack.html.haml @@ -69,7 +69,7 @@ .jamtrack-price {{"$ " + data.jamtrack.price}} = "{% if (data.jamtrack.added_cart) { %}" - %a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Added to Cart + %a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Purchased = "{% } else { %}" %a.jamtrack-add-cart.button-orange{href: "#", "data-jamtrack-id" => "{{data.jamtrack.id}}"} Add to Cart = "{% }; %}" diff --git a/web/spec/features/jamtrack_shopping_spec.rb b/web/spec/features/jamtrack_shopping_spec.rb index 85684725d..049f0101a 100644 --- a/web/spec/features/jamtrack_shopping_spec.rb +++ b/web/spec/features/jamtrack_shopping_spec.rb @@ -53,7 +53,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature end if options[:added_cart] - jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Added to Cart') + jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Purchased') else jamtrack_record.find('a.jamtrack-add-cart.button-orange', text: 'Add to Cart') end From dcf3706099eb3346ba5b62ae31bd37c27b438a8a Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 20 Feb 2015 17:53:18 -0600 Subject: [PATCH 8/8] VRFS-2798 : Hide/show shopping cart icon depending on contents. --- web/app/assets/javascripts/layout.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index 6c40f596d..ab1c98813 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -275,6 +275,15 @@ $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration); } + function displayCartIcon(carts) { + var cartLink = $("a[href='" + "/client#/shoppingCart" + "']") + if (carts.length > 0) { + cartLink.removeClass("hidden") + } else { + cartLink.addClass("hidden") + } + } + function layoutHeader(screenWidth, screenHeight) { var width = screenWidth - 2 * opts.gutter; var height = opts.headerHeight - opts.gutter; @@ -287,6 +296,8 @@ left: left + "px" }; $('[layout="header"]').css(css); + + rest.getShoppingCarts().done(displayCartIcon) } function layoutNotify(screenWidth, screenHeight) {