diff --git a/admin/.rakeTasks b/admin/.rakeTasks deleted file mode 100644 index 78308c2e6..000000000 --- a/admin/.rakeTasks +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/admin/Gemfile b/admin/Gemfile index 91f5e0643..b9099a250 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -72,6 +72,7 @@ gem 'jquery-ui-rails'# , '5.0.5' #, '4.2.1' gem 'jquery-rails'# , '4.1.1' # both this and jquery-ui-rails are pinned; if you unpin, jquery/autocomplete is missing during precomplie gem 'rails-jquery-autocomplete' # This is the maintained version of rails3-jquery-autocomplete gem 'activeadmin' #, '1.0.0.pre4'# github: 'activeadmin', branch: 'master' +gem 'activeadmin-searchable_select' gem 'mime-types', '1.25' #gem 'meta_search' gem 'fog' diff --git a/admin/Gemfile.lock b/admin/Gemfile.lock index 141ff9ea3..8abef39c6 100644 --- a/admin/Gemfile.lock +++ b/admin/Gemfile.lock @@ -47,6 +47,10 @@ GEM ransack (>= 1.8.7) sass (~> 3.1) sprockets (< 4.1) + activeadmin-searchable_select (1.4.0) + activeadmin (>= 1.x, < 3) + jquery-rails (>= 3.0, < 5) + select2-rails (~> 4.0) activeadmin_addons (1.7.1) active_material railties @@ -695,6 +699,7 @@ PLATFORMS DEPENDENCIES aasm activeadmin + activeadmin-searchable_select activeadmin_addons amqp (= 0.9.8) auto_strip_attributes (= 2.6.0) @@ -788,7 +793,7 @@ DEPENDENCIES zip-codes RUBY VERSION - ruby 2.3.1p112 + ruby 2.4.1p111 BUNDLED WITH 1.17.3 diff --git a/admin/app/admin/affiliate_cohorts.rb b/admin/app/admin/affiliate_cohorts.rb new file mode 100644 index 000000000..d1f4d5a6c --- /dev/null +++ b/admin/app/admin/affiliate_cohorts.rb @@ -0,0 +1,108 @@ +class AffiliateCohortsHelper + def self.percentage(opTop, opBottom) + "#{(opTop/opBottom * 100).round(1)}%" + end + + def self.quarter(date) + case date.month + when 1, 2, 3 then 0 + when 4, 5, 6 then 1 + when 7, 8, 9 then 2 + when 10, 11, 12 then 3 + end + end + + def self.payments_for_months(affiliate_partner, year, start_month, end_month) + JamRuby::AffiliateMonthlyPayment.where( + "affiliate_partner_id = ? AND month >= ? AND month <= ? AND year = ?", + affiliate_partner.id, + start_month, + end_month, + year + ).order('month DESC') + end + + def self.payments_for_quarter(affiliate_partner, year, quarter) + JamRuby::AffiliateQuarterlyPayment.where( + "affiliate_partner_id = ? AND quarter = ? AND year = ?", + affiliate_partner.id, + quarter, + year + ).order('quarter DESC') + end + + def self.all_time_payments(affiliate_partner) + JamRuby::AffiliateQuarterlyPayment.where( + "affiliate_partner_id = ?", affiliate_partner.id + ) + end + + def self.current_quarter_payments(affiliate_partner) + AffiliateCohortsHelper.payments_for_quarter(affiliate_partner, + Date.today.year, + AffiliateCohortsHelper.quarter(Date.today) + ) + end + + def self.current_quarter_monthly_payments(affiliate_partner) + AffiliateCohortsHelper.payments_for_months(affiliate_partner, + Date.today.beginning_of_quarter.year, + Date.today.beginning_of_quarter.month, + Date.today.end_of_quarter.month + ) + end + + def self.prior_quarter_payments(affiliate_partner) + prev_quarter_start = (Date.today.beginning_of_quarter - 1.day).beginning_of_quarter + prev_quarter = AffiliateCohortsHelper.quarter(prev_quarter_start) + AffiliateCohortsHelper.payments_for_quarter(affiliate_partner, + prev_quarter_start.year, + prev_quarter + ) + end + + def self.prior_quarter_payable_amount(affiliate_partner) + total = AffiliateCohortsHelper.prior_quarter_payments(affiliate_partner).inject(0.0){ | sum, payment | + sum += payment.due_amount_in_cents } + paid = AffiliateCohortsHelper.prior_quarter_payments(affiliate_partner).where(paid: false).inject(0.0){ | sum, payment | + sum += payment.due_amount_in_cents } + (total - paid) / 100.0 + end +end + +ActiveAdmin.register_page "Affiliate Cohorts" do + menu parent: 'Reports' + + content :title => "Affiliate Cohorts" do + table_for AffiliatePartner.includes(:partner_user).all do + column 'Affiliate Name' do |partner| + link_to partner.partner_name, admin_affiliate_path(partner) + end + column 'Affiliate ID', :id + column 'Affiliate Email', Proc.new{ | partner | partner.partner_user.email} + column 'Affiliate Paypal', Proc.new{| partner | partner&.paypal_id } + column 'All Time Users', :referral_user_count + column 'All Time Subscribers', Proc.new{ | partner | partner.subscribed_user_referrals.size } + column 'All Time Subscriber Conversion Rate', Proc.new{ | partner | + AffiliateCohortsHelper.percentage(partner.subscribed_user_referrals.size.to_f, partner.referral_user_count.to_f) } + column 'All Time Revenues', Proc.new{ | partner | + number_to_currency(AffiliateCohortsHelper.all_time_payments(partner).inject(0.0){ | sum, payment | sum += payment.due_amount_in_cents } / 100.0) + } + column 'Current Quarter Revenues', Proc.new{ | partner | + number_to_currency(AffiliateCohortsHelper.current_quarter_payments(partner).inject(0.0){ | sum, payment | sum += payment.due_amount_in_cents } / 100.0 ) + } + column 'Current Quarter Revenues by Month', Proc.new{ | partner | + AffiliateCohortsHelper.current_quarter_monthly_payments(partner).each do |monthly_payment| + li "#{Date::MONTHNAMES[monthly_payment.month]} #{monthly_payment.year} - #{number_to_currency(monthly_payment.due_amount_in_cents.to_f / 100.0)}" + end + ''.html_safe + } + column 'Prior Quarter Revenues', Proc.new{ | partner | + number_to_currency(AffiliateCohortsHelper.prior_quarter_payments(partner).inject(0.0){ | sum, payment | sum += payment.due_amount_in_cents } / 100.0) + } + column 'Prior Quarter Payable', Proc.new{ | partner | + number_to_currency(AffiliateCohortsHelper.prior_quarter_payable_amount(partner)) + } + end + end +end \ No newline at end of file diff --git a/admin/app/admin/affiliate_links.rb b/admin/app/admin/affiliate_links.rb new file mode 100644 index 000000000..2080553b2 --- /dev/null +++ b/admin/app/admin/affiliate_links.rb @@ -0,0 +1,33 @@ +ActiveAdmin.register JamRuby::AffiliateLink, :as => 'Affiliate Links' do + + menu :label => 'Links', :parent => 'Affiliates' + + config.sort_order = 'created_at ASC' + config.batch_actions = false + # config.clear_action_items! + config.filters = false + config.per_page = 50 + config.paginate = true + + #form :partial => 'form' + + + form do |f| + f.inputs 'Fields' do + f.input(:name, :input_html => { :maxlength => 255 }) + f.input(:link, :input_html => { :maxlength => 255 }) + end + f.actions + end + + index do + + column 'Name' do |oo| + oo.name + end + column 'Link' do |oo| + oo.link + end + actions + end +end diff --git a/admin/app/admin/affiliates.rb b/admin/app/admin/affiliates.rb index 2ff8813e3..dc99718c4 100644 --- a/admin/app/admin/affiliates.rb +++ b/admin/app/admin/affiliates.rb @@ -9,22 +9,51 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do config.per_page = 50 config.paginate = true - form :partial => 'form' + #form :partial => 'form' scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') } scope("Unpaid") { |partner| partner.unpaid } + controller do + helper 'active_admin/subscription' + end + + form do |f| + f.inputs 'Fields' do + f.input(:partner_name, :input_html => { :maxlength => 128 }) + f.input(:partner_user, as: :searchable_select, hint: 'This person is the owner of the affiliate. Has access to reporting info in account section of www.jamkazam.com') + f.input(:entity_type, :as => :select, :collection => AffiliatePartner::ENTITY_TYPES) + f.input(:rate) + f.input(:paypal_id) + end + f.actions + end + index do # actions # use this for all view/edit/delete links - column 'User' do |oo| link_to(oo.partner_user.name, admin_user_path(oo.partner_user.id), {:title => oo.partner_user.name}) end - column 'Name' do |oo| oo.partner_name end - column 'Type' do |oo| oo.entity_type end - column 'Code' do |oo| oo.id end - column 'Referral Count' do |oo| oo.referral_user_count end - column 'Earnings' do |oo| sprintf("$%.2f", oo.cumulative_earnings_in_dollars) end - column 'Amount Owed' do |oo| sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) end + column 'User' do |oo| + link_to(oo.partner_user.name, admin_user_path(oo.partner_user.id), { :title => oo.partner_user.name }) + end + column 'Name' do |oo| + oo.partner_name + end + column 'Type' do |oo| + oo.entity_type + end + column 'Code' do |oo| + oo.id + end + column 'Referral Count' do |oo| + oo.referral_user_count + end + column 'Earnings' do |oo| + sprintf("$%.2f", oo.cumulative_earnings_in_dollars) + end + column 'Amount Owed' do |oo| + sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) + end column 'Pay Actions' do |oo| link_to('Mark Paid', mark_paid_admin_affiliate_path(oo.id), :confirm => "Mark this affiliate as PAID?") if oo.unpaid end @@ -32,6 +61,22 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do actions end + show do |affiliate_partner| + + attributes_table do + row :id + row :partner_name + row :entity_type + row :rate + row :address + row :tax_identifier + row :paypal_id + end + + + render 'earnings', { affiliate_partner: affiliate_partner } + + end action_item :only => [:show] do link_to("Mark Paid", diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index 58dbe92f1..aaf2b70ca 100644 --- a/admin/app/admin/jam_ruby_users.rb +++ b/admin/app/admin/jam_ruby_users.rb @@ -1,5 +1,11 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do + searchable_select_options(scope: User.all, + text_attribute: :username, + filter: lambda do |term, scope| + scope.ransack(full_name_or_email_cont: term).result + end) + collection_action :autocomplete_user_email, :method => :get actions :all, :except => [:destroy] diff --git a/admin/app/assets/javascripts/active_admin.js b/admin/app/assets/javascripts/active_admin.js index df9db3400..a79135b71 100644 --- a/admin/app/assets/javascripts/active_admin.js +++ b/admin/app/assets/javascripts/active_admin.js @@ -9,6 +9,7 @@ // require jquery.ui.autocomplete //= require cocoon //= require active_admin/base +//= require active_admin/searchable_select // //= require autocomplete-rails //= require base //= require_tree . diff --git a/admin/app/assets/stylesheets/active_admin.css.scss b/admin/app/assets/stylesheets/active_admin.css.scss index f5bb8a9c2..05c71f28a 100644 --- a/admin/app/assets/stylesheets/active_admin.css.scss +++ b/admin/app/assets/stylesheets/active_admin.css.scss @@ -15,6 +15,7 @@ // Active Admin's got SASS! @import "active_admin/mixins"; @import "active_admin/base"; +@import "active_admin/searchable_select"; // Overriding any non-variable SASS must be done after the fact. // For example, to change the default status-tag color: diff --git a/admin/app/helpers/active_admin/subscription_helper.rb b/admin/app/helpers/active_admin/subscription_helper.rb new file mode 100644 index 000000000..5b5344275 --- /dev/null +++ b/admin/app/helpers/active_admin/subscription_helper.rb @@ -0,0 +1,20 @@ +module ActiveAdmin + module SubscriptionHelper + def subscription_plan_name(code) + case code + when 'jamrubysilver' + 'Silver' + when 'jamrubygold' + 'Gold' + when 'jamrubyplatinum' + 'Platinum' + when 'jamsubgoldyearly' + 'Gold Yearly' + when 'jamsubsilveryearly' + 'Silver Yearly' + when 'jamsubplatinumyearly' + 'Platinum Yearly' + end + end + end +end \ No newline at end of file diff --git a/admin/app/helpers/application_helper.rb b/admin/app/helpers/application_helper.rb index c1545e10f..2584194eb 100644 --- a/admin/app/helpers/application_helper.rb +++ b/admin/app/helpers/application_helper.rb @@ -39,5 +39,4 @@ module ApplicationHelper end - end diff --git a/admin/app/views/admin/affiliates/_earnings.html.erb b/admin/app/views/admin/affiliates/_earnings.html.erb new file mode 100644 index 000000000..9e0b08dae --- /dev/null +++ b/admin/app/views/admin/affiliates/_earnings.html.erb @@ -0,0 +1,42 @@ +

Earnings by Month

+ + + + + + + + + + + <% AffiliateMonthlyPayment.index(affiliate_partner.partner_user, {})[0].each do |payment| %> + + + + + + + <% end %> + +
MonthJamTracksSubscriptionsAffiliate Earnings
+ <%= Date::MONTHNAMES[payment.month] if payment.month %> + <%= payment.year %> + + <% if payment.jamtracks_sold > 0 %> + JamTracks: <%= pluralize payment.jamtracks_sold, 'unit' %> + <% end %> + + <% + month_start = Date.new(payment.year, payment.month, 1) + month_end = Date.new(payment.year, payment.month, 1).end_of_month + AffiliateDistribution.subscription_plans_count(affiliate_partner.id, month_start, month_end).each do |plan_count| + %> +
+ <%= subscription_plan_name(plan_count[:plan]) -%>: <%= pluralize(plan_count.count, 'unit') -%> sold +
+ <% + end + %> +
+ <%= number_to_currency(payment.due_amount_in_cents.to_f/100.0) %> +
\ No newline at end of file diff --git a/admin/app/views/admin/affiliates/_form.html.erb b/admin/app/views/admin/affiliates/_form.html.erb index 78a1dce70..cc27cb378 100644 --- a/admin/app/views/admin/affiliates/_form.html.erb +++ b/admin/app/views/admin/affiliates/_form.html.erb @@ -2,6 +2,7 @@ <%= f.semantic_errors *f.object.errors.keys %> <%= f.inputs do %> <%= f.input(:partner_name, :input_html => {:maxlength => 128}) %> + <%= f.input(:partner_user, as: :searchable_select, ajax: true, hint: 'The user that manages/owns this affiliate. They can see affiliate reports') %> <%= f.input(:entity_type, :as => :select, :collection => AffiliatePartner::ENTITY_TYPES) %> <%= f.input(:rate) %> <% end %> diff --git a/ruby/Gemfile b/ruby/Gemfile index d5970f415..82e428488 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -39,7 +39,7 @@ gem 'fog-brightbox', '0.11.0' # pinned until we are on ruby 2.5; then remove gem 'faraday', '0.9.2' # pinned untnil we are on ruby 2.5; then remove gem 'ruby-prof', '0.15.9' # pinned until we are on ruby 2.5; then remove gem 'rubyzip', '1.2.0' # pinned until we are on ruby 2.5; then remove -gem 'recurly', '2.7.0' # should upgrade to 3.x when we have time to validaate +gem 'recurly', '2.18.16' # should upgrade to 3.x when we have time to validaate gem 'icalendar', '2.4.0' # pinned until we are on ruby 2.5; then remove gem 'email_validator', '1.6.0' # pinned until we are on ruby 2.5, then remove gem 'redis', '3.3.0' # pinned until we are on 2.5; then remove @@ -103,6 +103,7 @@ group :test do gem 'time_difference' # gem 'byebug' gem 'stripe-ruby-mock' + gem 'webmock', '~> 3.11', '>= 3.11.2' end diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index b214832f4..1af07266c 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -51,6 +51,8 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) aliyun-sdk (0.8.0) nokogiri (~> 1.6) rest-client (~> 2.0) @@ -80,6 +82,8 @@ GEM json (>= 1.7) coderay (1.1.3) concurrent-ruby (1.1.8) + crack (0.4.5) + rexml crass (1.0.6) dante (0.2.0) database_cleaner (1.4.1) @@ -272,6 +276,7 @@ GEM rails (>= 3.0) globalid (0.4.2) activesupport (>= 4.2.0) + hashdiff (1.0.1) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) @@ -330,6 +335,7 @@ GEM pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) + public_suffix (4.0.6) raabro (1.4.0) rack (1.6.13) rack-protection (1.5.5) @@ -368,7 +374,7 @@ GEM json (>= 1.8) nokogiri (~> 1.5) optimist (~> 3.0) - recurly (2.7.0) + recurly (2.18.16) redis (3.3.0) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) @@ -405,6 +411,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rexml (3.2.4) rspec (2.11.0) rspec-core (~> 2.11.0) rspec-expectations (~> 2.11.0) @@ -468,6 +475,10 @@ GEM rack (>= 1.0.0) warden (1.2.7) rack (>= 1.0) + webmock (3.11.2) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) will_paginate (3.3.0) xml-simple (1.1.8) xmlrpc (0.3.1) @@ -516,7 +527,7 @@ DEPENDENCIES pry rails-observers (= 0.1.2) railties (= 4.2.8) - recurly (= 2.7.0) + recurly (= 2.18.16) redis (= 3.3.0) redis-namespace (= 1.5.2) resque @@ -543,11 +554,12 @@ DEPENDENCIES time_difference timecop uuidtools (= 2.1.2) + webmock (~> 3.11, >= 3.11.2) will_paginate zip-codes RUBY VERSION - ruby 2.4.1p111 + ruby 2.3.1p112 BUNDLED WITH 1.17.3 diff --git a/ruby/db/migrate/20210202183522_create_init_structure.rb b/ruby/db/migrate/20210202183522_create_init_structure.rb index 99aa70212..f39f59e8a 100644 --- a/ruby/db/migrate/20210202183522_create_init_structure.rb +++ b/ruby/db/migrate/20210202183522_create_init_structure.rb @@ -1,5 +1,8 @@ class CreateInitStructure < ActiveRecord::Migration def up + # this can't apply in production or staging, -- and schema.rb captures this test/dev environments + return if ENV['RAILS_ENV'] == 'production' + ActiveRecord::Base.connection.execute(IO.read(File.expand_path("../../init_db.sql", __FILE__))) end end diff --git a/ruby/db/migrate/20210214142857_pay_pal_field_for_affiliate.rb b/ruby/db/migrate/20210214142857_pay_pal_field_for_affiliate.rb new file mode 100644 index 000000000..323280f21 --- /dev/null +++ b/ruby/db/migrate/20210214142857_pay_pal_field_for_affiliate.rb @@ -0,0 +1,18 @@ + class PayPalFieldForAffiliate < ActiveRecord::Migration + def self.up + execute "ALTER TABLE affiliate_partners ADD COLUMN paypal_id VARCHAR(255)" + execute %( + CREATE TABLE affiliate_links ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(255) NOT NULL, + link VARCHAR(1024) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + ) + end + def self.down + execute "DROP TABLE affiliate_links" + execute "ALTER TABLE affiliate_partners DROP COLUMN paypal_id" + end + end diff --git a/ruby/db/migrate/20210224171323_change_affiliate_distributions_sale_line_item_id_not_null.rb b/ruby/db/migrate/20210224171323_change_affiliate_distributions_sale_line_item_id_not_null.rb new file mode 100644 index 000000000..be31c4c8f --- /dev/null +++ b/ruby/db/migrate/20210224171323_change_affiliate_distributions_sale_line_item_id_not_null.rb @@ -0,0 +1,11 @@ + class ChangeAffiliateDistributionsSaleLineItemIdNotNull < ActiveRecord::Migration + def self.up + #change_column_null :affiliate_distributions, :sale_line_item_id, true + execute "ALTER TABLE affiliate_distributions ALTER COLUMN sale_line_item_id DROP NOT NULL" + end + + def self.down + #change_column_null :affiliate_distributions, :sale_line_item_id, false + execute "ALTER TABLE affiliate_distributions ALTER COLUMN sale_line_item_id SET NOT NULL" + end + end diff --git a/ruby/db/migrate/20210224182154_add_recurly_transactions_last_sync_at_to_generic_state.rb b/ruby/db/migrate/20210224182154_add_recurly_transactions_last_sync_at_to_generic_state.rb new file mode 100644 index 000000000..fd052ab84 --- /dev/null +++ b/ruby/db/migrate/20210224182154_add_recurly_transactions_last_sync_at_to_generic_state.rb @@ -0,0 +1,12 @@ + class AddRecurlyTransactionsLastSyncAtToGenericState < ActiveRecord::Migration + + def self.up + #add_column :generic_state, :recurly_transactions_last_sync_at, :datetime + execute "ALTER TABLE generic_state ADD COLUMN recurly_transactions_last_sync_at TIMESTAMP" + end + + def self.down + #remove_column :generic_state, :recurly_transactions_last_sync_at, :datetime + execute "ALTER TABLE generic_state REMOVE COLUMN recurly_transactions_last_sync_at" + end + end diff --git a/ruby/db/migrate/20210226112943_add_product_type_to_affiliate_distributions.rb b/ruby/db/migrate/20210226112943_add_product_type_to_affiliate_distributions.rb new file mode 100644 index 000000000..334d2e66e --- /dev/null +++ b/ruby/db/migrate/20210226112943_add_product_type_to_affiliate_distributions.rb @@ -0,0 +1,10 @@ + class AddProductTypeToAffiliateDistributions < ActiveRecord::Migration + + def self.up + execute("ALTER TABLE affiliate_distributions ADD COLUMN product_type VARCHAR(64)") + end + + def self.down + execute("ALTER TABLE affiliate_distributions REMOVE COLUMN product_type") + end + end diff --git a/ruby/db/migrate/20210226113811_set_default_for_affiliate_distributions_product_type.rb b/ruby/db/migrate/20210226113811_set_default_for_affiliate_distributions_product_type.rb new file mode 100644 index 000000000..7bd8e3316 --- /dev/null +++ b/ruby/db/migrate/20210226113811_set_default_for_affiliate_distributions_product_type.rb @@ -0,0 +1,11 @@ + class SetDefaultForAffiliateDistributionsProductType < ActiveRecord::Migration + + def self.up + execute("UPDATE affiliate_distributions SET product_type = 'JamTrack'") + execute("ALTER TABLE affiliate_distributions ALTER COLUMN product_type SET DEFAULT 'JamTrack'") + end + + def self.down + execute("ALTER TABLE affiliate_distributions ALTER COLUMN DROP DEFAULT") + end + end diff --git a/ruby/db/migrate/20210301233601_add_product_code_to_affiliate_distributions.rb b/ruby/db/migrate/20210301233601_add_product_code_to_affiliate_distributions.rb new file mode 100644 index 000000000..b2e518e8b --- /dev/null +++ b/ruby/db/migrate/20210301233601_add_product_code_to_affiliate_distributions.rb @@ -0,0 +1,9 @@ + class AddProductCodeToAffiliateDistributions < ActiveRecord::Migration + def self.up + execute("ALTER TABLE affiliate_distributions ADD COLUMN product_code VARCHAR(64)") + end + + def self.down + execute("ALTER TABLE affiliate_distributions DROP COLUMN IF EXISTS product_code") + end + end diff --git a/ruby/db/migrate/20210305055542_jam_track_share_in_cents_to_affiliate_partners.rb b/ruby/db/migrate/20210305055542_jam_track_share_in_cents_to_affiliate_partners.rb new file mode 100644 index 000000000..a9eac7b71 --- /dev/null +++ b/ruby/db/migrate/20210305055542_jam_track_share_in_cents_to_affiliate_partners.rb @@ -0,0 +1,11 @@ + class JamTrackShareInCentsToAffiliatePartners < ActiveRecord::Migration + + def self.up + execute("ALTER TABLE affiliate_partners ADD COLUMN jamtrack_share_in_cents NUMERIC(8,2)") + end + + def self.down + execute("ALTER TABLE affiliate_partners DROP COLUMN IF EXISTS jamtrack_share_in_cents") + end + + end diff --git a/ruby/db/migrate/20210305060423_set_default_values_of_jam_track_share_in_cents.rb b/ruby/db/migrate/20210305060423_set_default_values_of_jam_track_share_in_cents.rb new file mode 100644 index 000000000..5b43f08f5 --- /dev/null +++ b/ruby/db/migrate/20210305060423_set_default_values_of_jam_track_share_in_cents.rb @@ -0,0 +1,12 @@ + class SetDefaultValuesOfJamTrackShareInCents < ActiveRecord::Migration + + def self.up + execute("UPDATE affiliate_partners SET jamtrack_share_in_cents = 25") + execute("ALTER TABLE affiliate_partners ALTER COLUMN jamtrack_share_in_cents SET DEFAULT 25") + end + + def self.down + execute("ALTER TABLE affiliate_partners ALTER COLUMN DROP DEFAULT") + end + + end diff --git a/ruby/db/migrate/20210309111429_add_first_subscribed_at_to_users.rb b/ruby/db/migrate/20210309111429_add_first_subscribed_at_to_users.rb new file mode 100644 index 000000000..a921b4ec0 --- /dev/null +++ b/ruby/db/migrate/20210309111429_add_first_subscribed_at_to_users.rb @@ -0,0 +1,11 @@ + class AddFirstSubscribedAtToUsers < ActiveRecord::Migration + + def self.up + execute("ALTER TABLE users ADD COLUMN first_subscribed_at TIMESTAMP WITHOUT TIME ZONE") + execute("UPDATE users SET first_subscribed_at = NOW() WHERE recurly_subscription_id IS NOT NULL") + end + + def self.down + execute("ALTER TABLE users DROP COLUMN first_subscribed_at") + end + end diff --git a/ruby/db/migrate/20210310205805_add_external_id_to_affiliate_distributions.rb b/ruby/db/migrate/20210310205805_add_external_id_to_affiliate_distributions.rb new file mode 100644 index 000000000..ef5651f7f --- /dev/null +++ b/ruby/db/migrate/20210310205805_add_external_id_to_affiliate_distributions.rb @@ -0,0 +1,12 @@ + class AddExternalIdToAffiliateDistributions < ActiveRecord::Migration + + def self.up + execute("ALTER TABLE affiliate_distributions ADD COLUMN external_id character varying(64)") + execute("ALTER TABLE affiliate_distributions ADD CONSTRAINT affiliate_distributions_external_id_key UNIQUE (external_id)") + end + + def self.down + execute("ALTER TABLE affiliate_distributions DROP CONSTRAINT affiliate_distributions_external_id_key") + execute("ALTER TABLE affiliate_distributions DROP COLUMN external_id") + end + end diff --git a/ruby/db/migrate/20210311173309_change_affiliate_partners_rate_default.rb b/ruby/db/migrate/20210311173309_change_affiliate_partners_rate_default.rb new file mode 100644 index 000000000..0b48f9834 --- /dev/null +++ b/ruby/db/migrate/20210311173309_change_affiliate_partners_rate_default.rb @@ -0,0 +1,9 @@ + class ChangeAffiliatePartnersRateDefault < ActiveRecord::Migration + def self.up + execute("ALTER TABLE affiliate_partners ALTER COLUMN rate SET DEFAULT 0.30") + end + + def self.down + execute("ALTER TABLE affiliate_partners ALTER COLUMN rate SET DEFAULT 0.10") + end + end diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 478338039..e5a4c5a21 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -260,6 +260,7 @@ require "jam_ruby/models/affiliate_monthly_payment" require "jam_ruby/models/affiliate_traffic_total" require "jam_ruby/models/affiliate_referral_visit" require "jam_ruby/models/affiliate_payment" +require "jam_ruby/models/affiliate_link" require "jam_ruby/models/chat_message" require "jam_ruby/models/shopping_cart" require "jam_ruby/models/generic_state" diff --git a/ruby/lib/jam_ruby/init.rb b/ruby/lib/jam_ruby/init.rb index 0aad3313e..e1ac8b36e 100644 --- a/ruby/lib/jam_ruby/init.rb +++ b/ruby/lib/jam_ruby/init.rb @@ -11,7 +11,7 @@ case JamRuby::Environment.mode Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6" Recurly.subdomain = 'jamkazam-development' else - Recurly.api_key = "4631527f203b41848523125b3ae51341" + Recurly.api_key = "1d0f1bdd30fe403cb78a0663d0915e81" Recurly.subdomain = 'jamkazam-test' end diff --git a/ruby/lib/jam_ruby/models/affiliate_distribution.rb b/ruby/lib/jam_ruby/models/affiliate_distribution.rb index 68988748e..5402224cf 100644 --- a/ruby/lib/jam_ruby/models/affiliate_distribution.rb +++ b/ruby/lib/jam_ruby/models/affiliate_distribution.rb @@ -15,5 +15,18 @@ module JamRuby distribution.sale_line_item = sale_line_item distribution end + + def self.subscription_plans_count(affiliate_referral_id, start_at, end_at) + AffiliateDistribution.where( + affiliate_referral_id: affiliate_referral_id, + product_type: 'Subscription').where(" + DATE(affiliate_distributions.created_at) >= ?", start_at).where(" + DATE(affiliate_distributions.created_at) <= ?", end_at).group_by(&:product_code).map do |product_code, distributions| + { + plan: product_code, + count: distributions.size + } + end + end end end diff --git a/ruby/lib/jam_ruby/models/affiliate_link.rb b/ruby/lib/jam_ruby/models/affiliate_link.rb new file mode 100644 index 000000000..78c4dfa57 --- /dev/null +++ b/ruby/lib/jam_ruby/models/affiliate_link.rb @@ -0,0 +1,7 @@ +class JamRuby::AffiliateLink < ActiveRecord::Base + + attr_accessible :link, :name, as: :admin + + validates :link, presence: true, length: {maximum: 1000} + validates :name, presence: true, length: {maximum: 255} +end diff --git a/ruby/lib/jam_ruby/models/affiliate_monthly_payment.rb b/ruby/lib/jam_ruby/models/affiliate_monthly_payment.rb index 8aa45ca5a..51113511b 100644 --- a/ruby/lib/jam_ruby/models/affiliate_monthly_payment.rb +++ b/ruby/lib/jam_ruby/models/affiliate_monthly_payment.rb @@ -25,7 +25,7 @@ class JamRuby::AffiliateMonthlyPayment < ActiveRecord::Base query = AffiliateMonthlyPayment .paginate(page: page, per_page: per_page) .where(affiliate_partner_id: user.affiliate_partner.id) - .order('year ASC, month ASC') + .order('year DESC, month DESC') if query.length == 0 [query, nil] diff --git a/ruby/lib/jam_ruby/models/affiliate_partner.rb b/ruby/lib/jam_ruby/models/affiliate_partner.rb index 733962264..607550fd2 100644 --- a/ruby/lib/jam_ruby/models/affiliate_partner.rb +++ b/ruby/lib/jam_ruby/models/affiliate_partner.rb @@ -12,6 +12,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base has_many :traffic_totals, :class_name => 'JamRuby::AffiliateTrafficTotal', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner has_many :visits, :class_name => 'JamRuby::AffiliateReferralVisit', foreign_key: :affiliate_partner_id, inverse_of: :affiliate_partner has_many :affiliate_distributions, :class_name => "JamRuby::AffiliateDistribution", foreign_key: :affiliate_referral_id + has_many :links, :class_name => "JamRuby::AffiliateLink", foreign_key: :affiliate_partner_id attr_accessible :partner_name, :partner_code, :partner_user_id, :entity_type, :rate, as: :admin ENTITY_TYPES = %w{ Individual Sole\ Proprietor Limited\ Liability\ Company\ (LLC) Partnership Trust/Estate S\ Corporation C\ Corporation Other } @@ -53,6 +54,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base before_save do |record| record.address ||= ADDRESS_SCHEMA.clone record.entity_type ||= ENTITY_TYPES.first + record.partner_user_id = nil if record.partner_user_id == '' #for activeadmin coercion end def display_name @@ -141,22 +143,25 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base end def created_within_affiliate_window(user, sale_time) - sale_time - user.created_at < 2.years + sale_time - user.created_at < 3.years end def should_attribute_sale?(shopping_cart, user_to_check, instance) - + raise "Not a JamTrack sale" if !shopping_cart.is_jam_track? + if created_within_affiliate_window(user_to_check, Time.now) product_info = shopping_cart.product_info(instance) # subtract the total quantity from the freebie quantity, to see how much we should attribute to them real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i - if shopping_cart.is_lesson? - applicable_rate = lesson_rate - else - applicable_rate = rate - end - {fee_in_cents: (product_info[:price] * 100 * real_quantity * applicable_rate.to_f).round} + # if shopping_cart.is_lesson? + # applicable_rate = lesson_rate + # else + # applicable_rate = rate + # end + #{fee_in_cents: (product_info[:price] * 100 * real_quantity * applicable_rate.to_f).round} + + { fee_in_cents: (real_quantity * jamtrack_share_in_cents.to_f).round} else false end @@ -283,6 +288,21 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base affiliate_distributions.affiliate_refunded = TRUE } end + + def self.subscription_distribution_sub_query(start_date, end_date, table_name) + %{ + FROM affiliate_distributions INNER JOIN affiliate_partners + ON affiliate_distributions.affiliate_referral_id = affiliate_partners.id + WHERE + affiliate_distributions.product_type = 'Subscription' + AND + (DATE(affiliate_distributions.created_at) >= DATE('#{start_date}')) + AND + (DATE(affiliate_distributions.created_at) <= DATE('#{end_date}')) + AND affiliate_distributions.affiliate_referral_id = #{table_name}.affiliate_partner_id + } + end + # total up quarters by looking in sale_line_items for items that are marked as having a affiliate_referral_id # don't forget to substract any sale_line_items that have a affiliate_refunded = TRUE def self.total_months(year, quarter) @@ -318,7 +338,11 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base (SELECT -SUM(affiliate_distributions.affiliate_referral_fee_in_cents) #{sale_items_refunded_subquery(start_date, end_date, 'affiliate_monthly_payments')} ), 0) - + + + COALESCE( + (SELECT SUM(affiliate_distributions.affiliate_referral_fee_in_cents) + #{subscription_distribution_sub_query(start_date, end_date, 'affiliate_monthly_payments')} + ), 0) WHERE closed = FALSE AND year = #{year} AND month = #{month} } @@ -371,6 +395,11 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base (SELECT -SUM(affiliate_distributions.affiliate_referral_fee_in_cents) #{sale_items_refunded_subquery(start_date, end_date, 'affiliate_quarterly_payments')} ), 0) + + + COALESCE( + (SELECT SUM(affiliate_distributions.affiliate_referral_fee_in_cents) + #{subscription_distribution_sub_query(start_date, end_date, 'affiliate_quarterly_payments')} + ), 0) WHERE closed = FALSE AND paid = FALSE AND year = #{year} AND quarter = #{quarter} } @@ -507,6 +536,18 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base AffiliatePartner::AFFILIATE_PARAMS + self.id.to_s end + # def subscribed_user_referrals + # user_referrals.joins(:sales).where("sales.sale_type = ?", Sale::SUBSCRIPTION_SALE) + # end + + def subscribed_user_referrals + user_referrals.where("first_subscribed_at IS NOT NULL") + end + + # def revenues_from_subscriptions + # subscribed_user_referrals.select("sales.recurly_total_in_cents").inject(0){ | sum, cent | sum += cent } / 100.0 + # end + def to_s display_name end diff --git a/ruby/lib/jam_ruby/models/affiliate_payment.rb b/ruby/lib/jam_ruby/models/affiliate_payment.rb index 1aa29f1c8..6d62f761e 100644 --- a/ruby/lib/jam_ruby/models/affiliate_payment.rb +++ b/ruby/lib/jam_ruby/models/affiliate_payment.rb @@ -28,14 +28,22 @@ module JamRuby limit = per_page + # query = AffiliatePayment + # .includes(affiliate_quarterly_payment: [], affiliate_monthly_payment:[]) + # .where(affiliate_partner_id: affiliate_partner_id) + # .where("(payment_type='quarterly' AND closed = true) OR payment_type='monthly'") + # .where('(paid = TRUE or due_amount_in_cents < 10000 or paid is NULL)') + # .paginate(:page => page, :per_page => limit) + # .order('year ASC, time_sort ASC, payment_type ASC') + query = AffiliatePayment - .includes(affiliate_quarterly_payment: [], affiliate_monthly_payment:[]) + .includes(:affiliate_monthly_payment => { :affiliate_partner => :affiliate_distributions }) .where(affiliate_partner_id: affiliate_partner_id) - .where("(payment_type='quarterly' AND closed = true) OR payment_type='monthly'") + .where("payment_type='monthly'") .where('(paid = TRUE or due_amount_in_cents < 10000 or paid is NULL)') .paginate(:page => page, :per_page => limit) - .order('year ASC, time_sort ASC, payment_type ASC') - + .order('year DESC, month DESC') + if query.length == 0 [query, nil] elsif query.length < limit diff --git a/ruby/lib/jam_ruby/models/affiliate_traffic_total.rb b/ruby/lib/jam_ruby/models/affiliate_traffic_total.rb index c85fa4fb5..efc0cf6b0 100644 --- a/ruby/lib/jam_ruby/models/affiliate_traffic_total.rb +++ b/ruby/lib/jam_ruby/models/affiliate_traffic_total.rb @@ -26,7 +26,7 @@ class JamRuby::AffiliateTrafficTotal < ActiveRecord::Base .paginate(page: page, per_page: per_page) .where(affiliate_partner_id: user.affiliate_partner.id) .where('visits != 0 OR signups != 0') - .order('day ASC') + .order('day DESC') if query.length == 0 [query, nil] diff --git a/ruby/lib/jam_ruby/models/generic_state.rb b/ruby/lib/jam_ruby/models/generic_state.rb index 2ef708430..7c02f5c4a 100644 --- a/ruby/lib/jam_ruby/models/generic_state.rb +++ b/ruby/lib/jam_ruby/models/generic_state.rb @@ -43,6 +43,10 @@ module JamRuby GenericState.singleton.event_page_top_logo_url end + def self.recurly_transactions_last_sync_at + GenericState.singleton.recurly_transactions_last_sync_at + end + def self.connection_policy GenericState.connection_policy end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index feb65edb8..ff2411310 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -385,6 +385,7 @@ module JamRuby #send_take_lesson_poke #first_lesson_instructions subscription_sync + subscription_transaction_sync end def self.subscription_sync @@ -422,6 +423,12 @@ module JamRuby @@log.info(msg) end + def self.subscription_transaction_sync + recurly_client = RecurlyClient.new + last_sync_at = GenericState.recurly_transactions_last_sync_at + recurly_client.sync_transactions({ begin_time: last_sync_at.iso8601 }) + end + def self.first_lesson_instructions User.came_through_amazon.joins(taken_lessons: [:music_session, :lesson_booking]) .where('lesson_bookings.recurring = FALSE') diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index dfe38a723..cf75d5bb6 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -344,6 +344,7 @@ module JamRuby begin subscription = create_subscription(current_user, plan_code, account, current_user.subscription_trial_ended? ? nil : current_user.subscription_trial_ends_at) current_user.recurly_subscription_id = subscription.uuid + current_user.first_subscribed_at = Time.now if current_user.first_subscribed_at.nil? if current_user.subscription_trial_ended? current_user.subscription_plan_code = get_highest_plan(subscription) current_user.subscription_plan_code_set_at = DateTime.now @@ -464,6 +465,7 @@ module JamRuby puts "Repairing subscription ID on account" user.update_attribute(:recurly_subscription_id, subscription.uuid) user.recurly_subscription_id = subscription.uuid + user.first_subscribed_at = Time.now if user.first_subscribed_at.nil? end return [subscription, account] @@ -637,6 +639,46 @@ module JamRuby end + def sync_transactions(options = {}) + ActiveRecord::Base.transaction do + options.merge!({ sort: :updated_at, state: :successful }) + Recurly::Transaction.find_each(options) do |transaction | + if AffiliateDistribution.find_by_external_id(transaction.uuid) + begin + Bugsnag.notify("ActiveRecord::RecordNotUnique: duplicate affiliate_distribution for Recurly transaction uuid #{transaction.uuid} was prevented from been added.") + rescue => exception + Rails.logger.error(exception) unless Rails.env.test? + end + next + end + + # these next lines try to ascertain that the transaction we've hit describes a true subscription + # jamtrack transactions are handled entirely separately, so this should avoid those, and perhaps other 'odd' + # transactions in Recurly + next if transaction.status != 'success' || transaction.source != 'subscription' + next if transaction.subscriptions.length == 0 + subscription = transaction.subscriptions.first + next if subscription.plan == nil || subscription.plan.plan_code == nil + + account = transaction.details["account"] + user = User.find(account.account_code) + affiliate_partner = user.affiliate_referral + if !affiliate_partner.nil? && affiliate_partner.created_within_affiliate_window(user, transaction.created_at.to_time) + affiliate_distribution = AffiliateDistribution.new + affiliate_distribution.product_type = "Subscription" + affiliate_distribution.affiliate_referral = affiliate_partner + fee_in_cents = transaction.amount_in_cents * affiliate_partner.rate + affiliate_distribution.affiliate_referral_fee_in_cents = fee_in_cents + affiliate_distribution.created_at = transaction.created_at.to_time + affiliate_distribution.product_code = subscription.plan.plan_code + affiliate_distribution.external_id = transaction.uuid #external_id is a unique column. should raises error if duplicates + affiliate_distribution.save! + end + end + GenericState.singleton.update_attribute(:recurly_transactions_last_sync_at, Time.now) + end + end + def find_or_create_account(current_user, billing_info, recurly_token = nil) account = get_account(current_user) @@ -653,6 +695,7 @@ module JamRuby end private + def account_hash(current_user, billing_info) options = { account_code: current_user.id, diff --git a/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb b/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb index 6e39d635e..4e4fa214e 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb @@ -8,7 +8,7 @@ module JamRuby def self.perform @@log.debug("waking up") - Teacher.randomize_order + #Teacher.randomize_order bounced_emails diff --git a/ruby/lib/jam_ruby/tasks/db/migrate.rake b/ruby/lib/jam_ruby/tasks/db/migrate.rake index 44fdfefca..43a648521 100644 --- a/ruby/lib/jam_ruby/tasks/db/migrate.rake +++ b/ruby/lib/jam_ruby/tasks/db/migrate.rake @@ -16,9 +16,23 @@ namespace :db do desc "Migrate the database" task :migrate do + version = ARGV[1] + if !version.nil? + version = version.to_i + end + ActiveRecord::Base.establish_connection(db_config) migrate_dir = File.expand_path("../../../../../db/migrate", __FILE__) - ActiveRecord::Migrator.migrate(migrate_dir) + ActiveRecord::Migrator.migrate(migrate_dir, version) + puts "#{ENV['RAILS_ENV']} database migrated." + end + + desc "Rollback the database" + task :rollback do + steps = (ARGV[1] || "1").to_i + ActiveRecord::Base.establish_connection(db_config) + migrate_dir = File.expand_path("../../../../../db/migrate", __FILE__) + ActiveRecord::Migrator.rollback(migrate_dir, steps) puts "#{ENV['RAILS_ENV']} database migrated." end @@ -48,7 +62,7 @@ namespace :db do namespace :g do desc "Generate migration" task :migration do - name = ARGV[1] || raise("Specify name: rake g:migration your_migration") + name = ARGV[1] || raise("Specify name: rake db:g:migration your_migration") timestamp = Time.now.strftime("%Y%m%d%H%M%S") path = File.expand_path("../../../../../db/migrate/#{timestamp}_#{name}.rb", __FILE__) migration_class = name.split("_").map(&:capitalize).join diff --git a/ruby/lib/jam_ruby/test_support.rb b/ruby/lib/jam_ruby/test_support.rb index 727d57a3f..7fe5e8a26 100644 --- a/ruby/lib/jam_ruby/test_support.rb +++ b/ruby/lib/jam_ruby/test_support.rb @@ -4,13 +4,13 @@ module JamRuby class TestSupport #helper for resetting test database - #drop create and execute db migrations + #drop create and execute db migrations def self.recreate_database ENV['RAILS_ENV'] = 'test' Rake.application.init Rake.application.load_rakefile begin - Rake::Task['db:jam_ruby:drop'].invoke + Rake::Task['db:jam_ruby:drop'].invoke Rake::Task['db:jam_ruby:create'].invoke Rake::Task['db:jam_ruby:migrate'].invoke rescue ActiveRecord::NoDatabaseError @@ -23,7 +23,9 @@ module JamRuby def self.migrate_database ENV['RAILS_ENV'] = 'test' - Rake.application.init + # invoke init in this way; otherwise any args passed to rspec will pass through to the rake task and blow it up. + # for instance, bundle exec rspec spec/some.rb -e "specific test" will cause a weird error + Rake.application.init('rake', []) Rake.application.load_rakefile Rake::Task['db:jam_ruby:migrate'].invoke end diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 8a831c50e..05555308a 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -885,6 +885,11 @@ FactoryGirl.define do legalese Faker::Lorem.paragraphs(6).join("\n\n") end + factory :affiliate_distribution, class: 'JamRuby::AffiliateDistribution' do + association :affiliate_referral, factory: :affiliate_partner + affiliate_referral_fee_in_cents 15 + end + factory :gift_card, class: 'JamRuby::GiftCard' do sequence(:code) { n.to_s } card_type JamRuby::GiftCardType::JAM_TRACKS_5 diff --git a/ruby/spec/jam_ruby/models/affiliate_distribution_spec.rb b/ruby/spec/jam_ruby/models/affiliate_distribution_spec.rb new file mode 100644 index 000000000..b6a1e061a --- /dev/null +++ b/ruby/spec/jam_ruby/models/affiliate_distribution_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe AffiliateDistribution do + let(:affiliate_partner){ FactoryGirl.create(:affiliate_partner) } + let(:affiliate_distribution1){ FactoryGirl.create(:affiliate_distribution, created_at: DateTime.new(2015, 1, 31), product_type: 'Subscription', product_code: 'jamsubsilver', affiliate_referral: affiliate_partner) } + let(:affiliate_distribution2){ FactoryGirl.create(:affiliate_distribution, created_at: DateTime.new(2015, 2, 1), product_type: 'Subscription', product_code: 'jamsubsilver', affiliate_referral: affiliate_partner) } + let(:affiliate_distribution3){ FactoryGirl.create(:affiliate_distribution, created_at: DateTime.new(2015, 2, 2), product_type: 'Subscription', product_code: 'jamsubgold', affiliate_referral: affiliate_partner) } + let(:affiliate_distribution4){ FactoryGirl.create(:affiliate_distribution, created_at: DateTime.new(2015, 2, 3), product_type: 'Subscription', product_code: 'jamsubgold', affiliate_referral: affiliate_partner) } + let(:affiliate_distribution5){ FactoryGirl.create(:affiliate_distribution, created_at: DateTime.new(2015, 2, 7), product_type: 'Subscription', product_code: 'jamsubplatinum', affiliate_referral: affiliate_partner) } + let(:affiliate_distribution6){ FactoryGirl.create(:affiliate_distribution, created_at: DateTime.new(2015, 3, 1), product_type: 'Subscription', product_code: 'jamsubsilver', affiliate_referral: affiliate_partner) } + + it "gives subscription plans counts for a partner between start and end dates" do + + affiliate_distribution1.reload + affiliate_distribution2.reload + affiliate_distribution3.reload + affiliate_distribution4.reload + affiliate_distribution5.reload + affiliate_distribution6.reload + + start_date = Date.new(2015, 2, 1) + end_date = Date.new(2015, 2, 7) + + expect(AffiliateDistribution.count).to eq(6) + expect(AffiliateDistribution.subscription_plans_count(affiliate_partner.id, start_date, end_date)).to eq( + [ + { plan: 'jamsubsilver', count: 1 }, + { plan: 'jamsubgold', count: 2 }, + { plan: 'jamsubplatinum', count: 1 } + ] + ) + + end +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/affiliate_monthly_payment_spec.rb b/ruby/spec/jam_ruby/models/affiliate_monthly_payment_spec.rb new file mode 100644 index 000000000..0579095f1 --- /dev/null +++ b/ruby/spec/jam_ruby/models/affiliate_monthly_payment_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe AffiliateMonthlyPayment do + let(:partner) { FactoryGirl.create(:affiliate_partner) } + + it ".index orders DESC" do + + monthly1 = FactoryGirl.create(:affiliate_monthly_payment, closed: true, month: 2, year: 2015, affiliate_partner: partner) + monthly2 = FactoryGirl.create(:affiliate_monthly_payment, closed: true, month: 3, year: 2015, affiliate_partner: partner) + monthly3 = FactoryGirl.create(:affiliate_monthly_payment, closed: true, month: 4, year: 2015, affiliate_partner: partner) + monthly4 = FactoryGirl.create(:affiliate_monthly_payment, closed: true, month: 1, year: 2016, affiliate_partner: partner) + + monthly_payments = AffiliateMonthlyPayment.index(partner.partner_user, {})[0] + expect(monthly_payments.map(&:year)).to eq [2016, 2015, 2015, 2015] + expect(monthly_payments.map(&:month)).to eq [1, 4, 3, 2] + end +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb b/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb index 7876b9bff..3917ef76c 100644 --- a/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb +++ b/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb @@ -61,16 +61,16 @@ describe AffiliatePartner do FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id) FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id) FactoryGirl.create(:user, :created_at => Time.now - 3.days, :affiliate_referral_id => partner.id) - FactoryGirl.create(:user, :created_at => Time.now - 2.days, :affiliate_referral_id => partner.id) + recent = FactoryGirl.create(:user, :created_at => Time.now - 2.days, :affiliate_referral_id => partner.id) partner.reload expect(partner.referral_user_count).to eq(6) by_date = partner.referrals_by_date expect(by_date.count).to eq(4) keys = by_date.keys - expect(keys.first).to eq(Date.parse((Time.now - 2.days).to_s)) + expect(keys.first).to eq(Date.parse((Time.now.utc - 2.days).to_s)) expect(by_date[keys.first]).to eq(1) - expect(keys.last).to eq(Date.parse((Time.now - 7.days).to_s)) + expect(keys.last).to eq(Date.parse((Time.now.utc - 7.days).to_s)) expect(by_date[keys.last]).to eq(2) end @@ -100,37 +100,44 @@ describe AffiliatePartner do describe "should_attribute_sale?" do + it "raise error if not a JamTrack sale" do + shopping_cart = double('shopping_cart', is_jam_track?: false) + user.affiliate_referral = partner + user.save! + expect{ partner.should_attribute_sale?(shopping_cart, user, jam_track)}.to raise_error(RuntimeError, "Not a JamTrack sale") + end + it "user with no affiliate relationship" do shopping_cart = ShoppingCart.create user, jam_track, 1 user.should_attribute_sale?(shopping_cart).should be_false end - it "user with an affiliate relationship buying a jamtrack" do + it "user with an affiliate relationship buying a JamTrack" do user.affiliate_referral = partner user.save! shopping_cart = ShoppingCart.create user, jam_track, 1, false - user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:20}) + user.should_attribute_sale?(shopping_cart).should eq({ fee_in_cents:25 } ) end - it "user with an affiliate relationship (with a custom rate) buying a jamtrack" do + it "user with an affiliate relationship (with a custom rate) buying a JamTrack. Custom rate should not attribute to affiliate share" do user.affiliate_referral = partner user.save! partner.rate = 0.25 partner.save! shopping_cart = ShoppingCart.create user, jam_track, 1, false - user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:50}) + user.should_attribute_sale?(shopping_cart).should eq( { fee_in_cents:25 } ) end it "user with an affiliate relationship redeeming a jamtrack" do user.affiliate_referral = partner user.save! shopping_cart = ShoppingCart.create user, jam_track, 1, true - user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:0}) + user.should_attribute_sale?(shopping_cart).should eq({ fee_in_cents:0 }) end it "user with an expired affiliate relationship redeeming a jamtrack" do user.affiliate_referral = partner - user.created_at = (365 * 2 + 1).days.ago + user.created_at = (365 * 3 + 1).days.ago user.save! shopping_cart = ShoppingCart.create user, jam_track, 1, false user.should_attribute_sale?(shopping_cart).should be_false @@ -142,20 +149,26 @@ describe AffiliatePartner do partner.created_within_affiliate_window(user, Time.now).should be_true end - it "user created 2 years, 1 day asgo" do - days_future = 365 * 2 + 1 - + it "user created 3 years, 1 day ago" do + days_future = 365 * 3 + 1 partner.created_within_affiliate_window(user, days_future.days.from_now).should be_false end + + it "user created 1 day before 3 years" do + days_future = 365 * 3 - 1 + partner.created_within_affiliate_window(user, days_future.days.from_now).should be_true + end end describe "tally_up" do let(:partner1) {FactoryGirl.create(:affiliate_partner)} let(:partner2) {FactoryGirl.create(:affiliate_partner)} + #let(:partner3) {FactoryGirl.create(:affiliate_partner)} let(:payment1) {FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner1)} let(:user_partner1) { FactoryGirl.create(:user, affiliate_referral: partner1)} let(:user_partner2) { FactoryGirl.create(:user, affiliate_referral: partner2)} let(:sale) {Sale.create_jam_track_sale(user_partner1)} + describe "ensure_quarters_exist" do @@ -335,11 +348,10 @@ describe AffiliatePartner do quarter.last_updated.should_not be_nil end - it "totals with sales data" do + it "totals with JamTrack sales" do partner1.touch partner2.touch - # create a freebie for partner1 shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, true freebie_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) @@ -349,8 +361,6 @@ describe AffiliatePartner do freebie_sale.affiliate_distributions.first.save! freebie_sale.save! - - AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliateQuarterlyPayment.count.should eq(2) AffiliatePartner.total_quarters(2015, 0) @@ -364,7 +374,7 @@ describe AffiliatePartner do # create a real sale for partner1 shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 1, 1) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -372,7 +382,7 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) quarter = partner2.quarters.first quarter.due_amount_in_cents.should eq(0) @@ -381,7 +391,7 @@ describe AffiliatePartner do # create a real sale for partner2 shopping_cart = ShoppingCart.create user_partner2, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 1, 1) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -390,16 +400,16 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) # create a real sale for partner1 shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 1, 1) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -407,10 +417,10 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(40) + quarter.due_amount_in_cents.should eq(50) quarter.jamtracks_sold.should eq(2) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) @@ -423,16 +433,16 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(40) + quarter.due_amount_in_cents.should eq(50) quarter.jamtracks_sold.should eq(2) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) # create a real sale but in previous quarter (should no have effect on the quarter being computed) shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2014, 12, 31) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -440,16 +450,16 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(40) + quarter.due_amount_in_cents.should eq(50) quarter.jamtracks_sold.should eq(2) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) # create a real sale but in later quarter (should no have effect on the quarter being computed) shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 4, 1) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -458,16 +468,16 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(40) + quarter.due_amount_in_cents.should eq(50) quarter.jamtracks_sold.should eq(2) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) # create a real sale but then refund it shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 3, 31) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -475,10 +485,10 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(60) + quarter.due_amount_in_cents.should eq(75) quarter.jamtracks_sold.should eq(3) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) # now refund it real_sale.affiliate_refunded_at = Date.new(2015, 3, 1) real_sale.affiliate_refunded = true @@ -489,9 +499,10 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 0) AffiliatePartner.total_quarters(2015, 0) quarter = partner1.quarters.first - quarter.due_amount_in_cents.should eq(40) + quarter.due_amount_in_cents.should eq(50) + quarter.jamtracks_sold.should eq(2) quarter = partner2.quarters.first - quarter.due_amount_in_cents.should eq(20) + quarter.due_amount_in_cents.should eq(25) quarter.jamtracks_sold.should eq(1) @@ -499,7 +510,7 @@ describe AffiliatePartner do AffiliatePartner.ensure_quarters_exist(2015, 1) AffiliatePartner.total_quarters(2015, 1) payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id) - payment.due_amount_in_cents.should eq(20) + payment.due_amount_in_cents.should eq(25) # and now refund it in the 3rd quarter real_sale_later.affiliate_refunded_at = Date.new(2015, 7, 1) @@ -510,16 +521,139 @@ describe AffiliatePartner do real_sale_later.affiliate_distributions.first.save! AffiliatePartner.total_quarters(2015, 1) payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id) - payment.due_amount_in_cents.should eq(20) + payment.due_amount_in_cents.should eq(25) payment.jamtracks_sold.should eq(1) # now catch the one refund in the 3rd quarter AffiliatePartner.ensure_quarters_exist(2015, 2) AffiliatePartner.total_quarters(2015, 2) payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(2, 2015, partner1.id) - payment.due_amount_in_cents.should eq(-20) + payment.due_amount_in_cents.should eq(-25) payment.jamtracks_sold.should eq(-1) + end + + it "totals subscriptions with JamTrack sales" do + + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubsliver', + affiliate_referral: partner1, + affiliate_referral_fee_in_cents: 15, + created_at: Date.new(2015, 1, 1) + ) + partner1.touch + + AffiliatePartner.ensure_quarters_exist(2015, 0) + AffiliatePartner.total_quarters(2015, 0) + + quarter = partner1.quarters.first + quarter.due_amount_in_cents.should eq(15) + + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubgold', + affiliate_referral: partner2, + affiliate_referral_fee_in_cents: 30, + created_at: Date.new(2015, 1, 1) + ) + partner2.touch + + AffiliatePartner.ensure_quarters_exist(2015, 0) + AffiliatePartner.total_quarters(2015, 0) + + quarter = partner2.quarters.first + expect(quarter).not_to eq(nil) + quarter.due_amount_in_cents.should eq(30) + + #subscribe by non-affiliate user should not create affiliate payments + FactoryGirl.create(:user) + AffiliatePartner.ensure_quarters_exist(2015, 0) + expect { AffiliatePartner.total_quarters(2015, 0)}.to change(AffiliateQuarterlyPayment, :count).by(0) + + # lets add JamTrack sale + # create a real sale by user1 who is affiliated with partner1 + shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false + real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) + real_sale.affiliate_referral_fee_in_cents.should eq(25) + real_sale.created_at = Date.new(2015, 1, 1) + real_sale.save! + real_sale.affiliate_distributions.first.created_at = real_sale.created_at + real_sale.affiliate_distributions.first.save! + AffiliatePartner.ensure_quarters_exist(2015, 0) + AffiliatePartner.total_quarters(2015, 0) + quarter = partner1.quarters.first + quarter.due_amount_in_cents.should eq(40) + quarter.jamtracks_sold.should eq(1) + + quarter = partner2.quarters.first + quarter.due_amount_in_cents.should eq(30) + quarter.jamtracks_sold.should eq(0) + + # create a real sale for user2 who is affiliated with partner2 + shopping_cart = ShoppingCart.create user_partner2, jam_track, 1, false + real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) + real_sale.affiliate_referral_fee_in_cents.should eq(25) + real_sale.created_at = Date.new(2015, 1, 1) + real_sale.save! + real_sale.affiliate_distributions.first.created_at = real_sale.created_at + real_sale.affiliate_distributions.first.save! + + AffiliatePartner.ensure_quarters_exist(2015, 0) + AffiliatePartner.total_quarters(2015, 0) + quarter = partner1.quarters.first + quarter.due_amount_in_cents.should eq(40) + quarter.jamtracks_sold.should eq(1) + quarter = partner2.quarters.first + quarter.due_amount_in_cents.should eq(55) + quarter.jamtracks_sold.should eq(1) + + #affiliate earnings from subscriptions from previous quater should not attribute to quater been considered + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubgold', + affiliate_referral: partner1, + affiliate_referral_fee_in_cents: 30, + created_at: Date.new(2014, 12, 31) + ) + partner1.touch + + AffiliatePartner.ensure_quarters_exist(2015, 0) + AffiliatePartner.total_quarters(2015, 0) + + quarter = partner1.quarters.first + quarter.due_amount_in_cents.should eq(40) + quarter = partner2.quarters.first + quarter.due_amount_in_cents.should eq(55) + + #affiliate earnings from subscriptions of next quater should not attribute to quater been considered + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubgold', + affiliate_referral: partner2, + affiliate_referral_fee_in_cents: 30, + created_at: Date.new(2015, 04, 01) + ) + partner2.touch + + AffiliatePartner.ensure_quarters_exist(2015, 0) + AffiliatePartner.total_quarters(2015, 0) + + quarter = partner1.quarters.first + quarter.due_amount_in_cents.should eq(40) + quarter = partner2.quarters.first + quarter.due_amount_in_cents.should eq(55) + + #earnings in second quarter + AffiliatePartner.ensure_quarters_exist(2015, 1) + AffiliatePartner.total_quarters(2015, 1) + quarter = partner1.quarters[1] + quarter.due_amount_in_cents.should eq(0) + quarter = partner2.quarters[1] + quarter.due_amount_in_cents.should eq(30) + + + end end @@ -528,7 +662,7 @@ describe AffiliatePartner do AffiliatePartner.tally_up(Date.new(2015, 1, 1)) end - it "successive runs" do + it "successive for JamTrack Sales" do GenericState.singleton.affiliate_tallied_at.should be_nil AffiliatePartner.tally_up(Date.new(2015, 1, 1)) GenericState.singleton.affiliate_tallied_at.should_not be_nil @@ -560,6 +694,7 @@ describe AffiliatePartner do month_previous.due_amount_in_cents.should eq(0) month_previous.closed.should be_true month_previous.jamtracks_sold.should eq(0) + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id) month_previous.due_amount_in_cents.should eq(0) month_previous.closed.should be_true @@ -576,7 +711,7 @@ describe AffiliatePartner do shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 4, 1) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -589,7 +724,7 @@ describe AffiliatePartner do quarter.jamtracks_sold.should eq(0) quarter.closed.should be_true quarter2 = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id) - quarter2.due_amount_in_cents.should eq(20) + quarter2.due_amount_in_cents.should eq(25) quarter2.jamtracks_sold.should eq(1) quarter2.closed.should be_false @@ -606,7 +741,7 @@ describe AffiliatePartner do month.jamtracks_sold.should eq(0) month.closed.should be_true month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(4, 2015, partner1.id) - month.due_amount_in_cents.should eq(20) + month.due_amount_in_cents.should eq(25) month.jamtracks_sold.should eq(1) month.closed.should be_false month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(5, 2015, partner1.id) @@ -622,7 +757,7 @@ describe AffiliatePartner do shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) - real_sale.affiliate_referral_fee_in_cents.should eq(20) + real_sale.affiliate_referral_fee_in_cents.should eq(25) real_sale.created_at = Date.new(2015, 1, 1) real_sale.save! real_sale.affiliate_distributions.first.created_at = real_sale.created_at @@ -646,6 +781,109 @@ describe AffiliatePartner do month.jamtracks_sold.should eq(0) month.closed.should be_true end + + it "successive for affiliate subscriptions and JamTrack Sales" do + GenericState.singleton.affiliate_tallied_at.should be_nil + AffiliatePartner.tally_up(Date.new(2015, 1, 1)) + GenericState.singleton.affiliate_tallied_at.should_not be_nil + AffiliateQuarterlyPayment.count.should eq(0) + AffiliateMonthlyPayment.count.should eq(0) + + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubgold', + affiliate_referral: partner1, + affiliate_referral_fee_in_cents: 30, + created_at: Date.new(2015, 1, 1) + ) + + # partner is created + partner1.touch + + shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false + real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) + real_sale.affiliate_referral_fee_in_cents.should eq(25) + real_sale.created_at = Date.new(2015, 1, 1) + real_sale.save! + real_sale.affiliate_distributions.first.created_at = real_sale.created_at + real_sale.affiliate_distributions.first.save! + + + AffiliatePartner.tally_up(Date.new(2015, 1, 1)) + + AffiliateQuarterlyPayment.count.should eq(2) + AffiliateMonthlyPayment.count.should eq(6) + + quarter_previous = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(3, 2014, partner1.id) + quarter_previous.due_amount_in_cents.should eq(0) + quarter_previous.closed.should be_true + + quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id) + quarter.due_amount_in_cents.should eq(55) + quarter.closed.should be_false + + month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(10, 2014, partner1.id) + month_previous.due_amount_in_cents.should eq(0) + month_previous.closed.should be_true + month_previous.jamtracks_sold.should eq(0) + + month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(11, 2014, partner1.id) + month_previous.due_amount_in_cents.should eq(0) + month_previous.closed.should be_true + month_previous.jamtracks_sold.should eq(0) + + month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(12, 2014, partner1.id) + month_previous.due_amount_in_cents.should eq(0) + month_previous.closed.should be_true + month_previous.jamtracks_sold.should eq(0) + + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id) + month.due_amount_in_cents.should eq(55) + month.closed.should be_false + month.jamtracks_sold.should eq(1) + + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id) + month.due_amount_in_cents.should eq(0) + month.closed.should be_false + month.jamtracks_sold.should eq(0) + + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id) + month.due_amount_in_cents.should eq(0) + month.closed.should be_false + month.jamtracks_sold.should eq(0) + + #user of partner 1 purchases a JamTrack in 1st quarter, which makes no sense, but proves that closed quarters are not touched + shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false + real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) + real_sale.affiliate_referral_fee_in_cents.should eq(25) + real_sale.created_at = Date.new(2015, 3, 1) + real_sale.save! + real_sale.affiliate_distributions.first.created_at = real_sale.created_at + real_sale.affiliate_distributions.first.save! + + AffiliatePartner.tally_up(Date.new(2015, 4, 1)) + + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id) + month.due_amount_in_cents.should eq(55) + month.closed.should be_true + month.jamtracks_sold.should eq(1) + + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id) + month.due_amount_in_cents.should eq(0) + month.closed.should be_true + month.jamtracks_sold.should eq(0) + + month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id) + month.due_amount_in_cents.should eq(0) #because this quarter has been closed before + month.closed.should be_true + month.jamtracks_sold.should eq(0) + + quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id) + quarter.due_amount_in_cents.should eq(55) + quarter.closed.should be_true + quarter.jamtracks_sold.should eq(1) + + end end describe "tally_traffic_totals" do diff --git a/ruby/spec/jam_ruby/models/affiliate_payment_spec.rb b/ruby/spec/jam_ruby/models/affiliate_payment_spec.rb index 14f9f4c13..21c12f15f 100644 --- a/ruby/spec/jam_ruby/models/affiliate_payment_spec.rb +++ b/ruby/spec/jam_ruby/models/affiliate_payment_spec.rb @@ -14,26 +14,32 @@ describe AffiliatePayment do monthly1 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 10, month: 1, year: 2015) monthly2 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 20, month: 2, year: 2015) monthly3 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 30, month: 3, year: 2015) - monthly4 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 40, month: 4, year: 2015) + monthly4 = FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner, closed: true, due_amount_in_cents: 40, month: 1, year: 2016) quarterly = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, closed: true, paid:true, due_amount_in_cents: 50, quarter: 0, year: 2015) + results, nex = AffiliatePayment.index(user_partner, {}) - results.length.should eq(5) + + results.length.should eq(4) result1 = results[0] result2 = results[1] result3 = results[2] result4 = results[3] - result5 = results[4] + #result5 = results[4] - result1.payment_type.should eq('monthly') - result1.due_amount_in_cents.should eq(10) - result2.payment_type.should eq('monthly') - result2.due_amount_in_cents.should eq(20) + result4.payment_type.should eq('monthly') + result4.due_amount_in_cents.should eq(10) result3.payment_type.should eq('monthly') - result3.due_amount_in_cents.should eq(30) - result4.payment_type.should eq('quarterly') - result4.due_amount_in_cents.should eq(50) - result5.payment_type.should eq('monthly') - result5.due_amount_in_cents.should eq(40) + result3.due_amount_in_cents.should eq(20) + result2.payment_type.should eq('monthly') + result2.due_amount_in_cents.should eq(30) + result1.payment_type.should eq('monthly') + result1.due_amount_in_cents.should eq(40) + + #NOTE: removeing quarter payments from AffiliatePayment.index + #"affiliate earnings" in client now only lists monthly payments + expect(results.map(&:payment_type)).not_to include("quarterly") + + end end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb b/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb index 04c28dfd6..bceaddf47 100644 --- a/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb +++ b/ruby/spec/jam_ruby/models/user_subscriptions_spec.rb @@ -1,13 +1,10 @@ require 'spec_helper' - +require 'webmock/rspec' describe "User Subscriptions" do let(:user1) {FactoryGirl.create(:user)} let(:client) { RecurlyClient.new } - before(:each) do - - end it "empty results" do user1.touch @@ -70,7 +67,443 @@ describe "User Subscriptions" do user1.subscription_last_checked_at.should_not be_nil user1.subscription_plan_code.should be_nil - end end + +describe 'Subscription transactions sync' do + let(:client) { RecurlyClient.new } + let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) } + let(:user) { FactoryGirl.create(:user, affiliate_referral: affiliate_partner) } + let(:user2) { FactoryGirl.create(:user, affiliate_referral: affiliate_partner) } + let(:transaction_response){ + transaction_response_xml(user) + } + 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] = '2025' + info[:verification_value] = '111' + info + } + describe "using recurly API over internet" do + it "fetches transactions created after GenericState.recurly_transactions_last_sync_at" do + # pending("test this directly on recurly without stubbing. [maybe can omit as it tests recurly api?]") + last_sync_at = Time.now + WebMock.allow_net_connect! + + # create user account and subscription in recurly + account1 = client.find_or_create_account(user, billing_info) + subscription1 = client.create_subscription(user, 'jamsubgold', account1, starts_at = nil) + subscription1.should_not be_nil + + expect { client.sync_transactions({ begin_time: last_sync_at.iso8601 }) }.to change(AffiliateDistribution, :count).by(1) + AffiliateDistribution.order(:created_at).last.external_id.should_not be_nil + AffiliateDistribution.order(:created_at).last.product_code.should_not be_nil + + GenericState.singleton.reload + + new_last_sync_at = GenericState.recurly_transactions_last_sync_at + expect(last_sync_at < new_last_sync_at).to be true + + # create second user account and subscription in recurly + account2 = client.find_or_create_account(user2, billing_info) + subscription2 = client.create_subscription(user2, 'jamsubplatinumyearly', account2, starts_at = nil) + + GenericState.singleton.update_attribute(:recurly_transactions_last_sync_at, new_last_sync_at) + expect { client.sync_transactions({ begin_time: new_last_sync_at.iso8601 }) }.to change(AffiliateDistribution, :count).by(1) + AffiliateDistribution.order(:created_at).last.external_id.should_not be_nil + AffiliateDistribution.order(:created_at).last.product_code.should_not be_nil + end + end + describe "using mocked recurly" do + before(:each) do + #allow(recurly_transaction).to receive(:find_each).and_return(transaction) #works in rspec >=2.14 + + WebMock.stub_request(:get, /recurly.com\/v2\/transactions\?\S+/). + to_return(status: 200, body: transaction_response, headers: {}) + + Recurly::Transaction.any_instance.stub(:subscriptions).and_return([ + Recurly::Subscription.new + ]) + + Recurly::Subscription.any_instance.stub(:plan).and_return([ + Recurly::Plan.new(plan_code: "jamsubgold") + ]) + end + + it "creates AffiliateDistribution records for successful recurring transactions" do + expect { client.sync_transactions }.to change(AffiliateDistribution, :count).by(2) + end + + # it "error out for when same transaction data been fetched" do + # expect { client.sync_transactions }.to change(AffiliateDistribution, :count).by(2) + # expect { client.sync_transactions }.to raise_error(ActiveRecord::RecordNotUnique) + # end + + it "does not create AffiliateDistribution for same transaction previously been created" do + expect { client.sync_transactions }.to change(AffiliateDistribution, :count).by(2) + expect { client.sync_transactions }.to change(AffiliateDistribution, :count).by(0) + end + + it "does not create AffiliateDistribution records when there is no affiliate partner" do + user.affiliate_referral = nil + user.save! + AffiliateDistribution.delete_all + transaction_response = transaction_response_xml(user) + WebMock.stub_request(:get, /recurly.com\/v2\/transactions\?\S+/). + to_return(status: 200, body: transaction_response, headers: {}) + expect { client.sync_transactions }.to change(AffiliateDistribution, :count).by(0) + end + + it "does not create AffiliateDistribution if out of affiliate window" do + AffiliateDistribution.delete_all + transaction_response = lapse_transaction_response_xml(user) + WebMock.stub_request(:get, /recurly.com\/v2\/transactions\?\S+/). + to_return(status: 200, body: transaction_response, headers: {}) + #expect { client.sync_transactions }.to change(AffiliateDistribution, :count).by(0) + client.sync_transactions + end + + it "assigns correct affiliate partner" do + client.sync_transactions + AffiliateDistribution.all.each do |affiliate_distribution| + expect(affiliate_distribution.affiliate_referral).to_not eq(nil) + expect(affiliate_distribution.affiliate_referral).to eq(affiliate_partner) + end + end + + it "updates affiliate referral fee" do + client.sync_transactions + most_recently_created = AffiliateDistribution.order(created_at: :desc).first + expect(most_recently_created.affiliate_referral_fee_in_cents).to_not eq(nil) + expect(most_recently_created.affiliate_referral_fee_in_cents).to eq(30) + end + + it "change affiliate rate and updates referral fee" do + affiliate_partner.rate = 0.20 + affiliate_partner.save! + client.sync_transactions + most_recently_created = AffiliateDistribution.order(created_at: :desc).first + expect(most_recently_created.affiliate_referral_fee_in_cents).to eq(20) + end + + it "sets subscription product_type" do + client.sync_transactions + AffiliateDistribution.all.each do |affiliate_distribution| + expect(affiliate_distribution.product_type).to_not eq(nil) + expect(affiliate_distribution.product_type).to eq('Subscription') + end + end + + it "sets subscription product_code" do + client.sync_transactions + AffiliateDistribution.all.each do |affiliate_distribution| + expect(affiliate_distribution.product_code).to_not eq(nil) + expect(affiliate_distribution.product_code).to eq('jamsubgold') + end + end + + it "does not error out if begin_time is nil" do + expect{ client.sync_transactions( { begin_time: nil } ) }.not_to raise_error + end + + it "changes GenericState.recurly_transactions_last_sync_at" do + before_time = GenericState.recurly_transactions_last_sync_at + client.sync_transactions + after_time = GenericState.recurly_transactions_last_sync_at + expect(before_time).not_to eq(after_time) + end + end + + +end + +def transaction_response_xml(user) + <<-XMLDATA + + + + + + 374adcf4d716c1afc7b0b64bb79d4381 + purchase + 100 + 9 + USD + success + credit_card + 2216615 + transaction + true + true + true + true + 127.0.0.1 + + Street address and postal code match. + + + #{10.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} + #{10.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} +
+ + #{user.id} + Verena + Example + New Company Name + verena@example.com + + Verena + Example + 123 Main St. + + San Francisco + CA + 94105 + US + + + Visa + 2019 + 12 + 411111 + 1111 + + +
+ +
+ + + + + + 374adcf4d716c1afc7b0b64bb79d4382 + purchase + 200 + 9 + USD + success + credit_card + 2216615 + transaction + true + true + true + true + 127.0.0.1 + + Street address and postal code match. + + + #{20.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} + #{20.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} +
+ + #{user.id} + Verena + Example + New Company Name + verena@example.com + + Verena + Example + 123 Main St. + + San Francisco + CA + 94105 + US + + + Visa + 2019 + 12 + 411111 + 1111 + + +
+
+ + + + + + + + 374adcf4d716c1afc7b0b64bb79d4383 + purchase + 200 + 9 + USD + failed + credit_card + 2216615 + transaction + true + true + true + true + 127.0.0.1 + + Street address and postal code match. + + + #{20.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} + #{20.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} +
+ + #{user.id} + Verena + Example + New Company Name + verena@example.com + + Verena + Example + 123 Main St. + + San Francisco + CA + 94105 + US + + + Visa + 2019 + 12 + 411111 + 1111 + + +
+
+ + + + + + + 374adcf4d716c1afc7b0b64bb79d4384 + purchase + 200 + 9 + USD + success + credit_card + 2216615 + transaction + false + true + true + true + 127.0.0.1 + + Street address and postal code match. + + + #{20.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} + #{20.minutes.ago.strftime('%Y-%m-%dT%H:%M:%SZ')} +
+ + #{user.id} + Verena + Example + New Company Name + verena@example.com + + Verena + Example + 123 Main St. + + San Francisco + CA + 94105 + US + + + Visa + 2019 + 12 + 411111 + 1111 + + +
+
+ + +
+XMLDATA +end + +def lapse_transaction_response_xml(user) + <<-XMLDATA + + + + + + 374adcf4d716c1afc7b0b64bb79d4385 + purchase + 100 + 9 + USD + success + credit_card + 2216615 + transaction + true + true + true + true + 127.0.0.1 + + Street address and postal code match. + + + #{(365.days + 1.day).ago.strftime('%Y-%m-%dT%H:%M:%SZ')} + #{(365.days + 1.day).ago.strftime('%Y-%m-%dT%H:%M:%SZ')} +
+ + #{user.id} + Verena + Example + New Company Name + verena@example.com + + Verena + Example + 123 Main St. + + San Francisco + CA + 94105 + US + + + Visa + 2019 + 12 + 411111 + 1111 + + +
+
+ + + +XMLDATA +end \ No newline at end of file diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index b4621403d..fa65dd796 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -119,6 +119,7 @@ Recurly::API.net_http = { end DatabaseCleaner.start + WebMock.allow_net_connect! example.run end diff --git a/web/Gemfile.lock b/web/Gemfile.lock index ed96855b8..535d86036 100644 --- a/web/Gemfile.lock +++ b/web/Gemfile.lock @@ -922,7 +922,7 @@ DEPENDENCIES zip-codes RUBY VERSION - ruby 2.3.1p112 + ruby 2.4.1p111 BUNDLED WITH 1.17.3 diff --git a/web/app/assets/javascripts/accounts_affiliate.js b/web/app/assets/javascripts/accounts_affiliate.js index f9b5ec02a..5eeefaa6f 100644 --- a/web/app/assets/javascripts/accounts_affiliate.js +++ b/web/app/assets/javascripts/accounts_affiliate.js @@ -13,7 +13,8 @@ var $screen = null; function beforeShow(data) { - userId = data.id; + + userId = context.JK.currentUserId affiliatePartnerData = null; } @@ -96,12 +97,15 @@ } function onAffiliateSignups(signups) { + + var traffics = signupsByMonth(signups.traffics); + console.log('traffics', traffics); var $table = $screen.find('table.traffic-table tbody') $table.empty(); var template = $('#template-affiliate-partner-signups-row').html(); - context._.each(signups.traffics, function(item) { + context._.each(traffics, function(item) { var $link = $(context._.template(template, item, {variable: 'data'})); var $day = $link.find('td.day') @@ -116,6 +120,41 @@ }) } + function signupsByMonth(data){ + var item, + i = 0, + groups = {}, + output = [], + date, year, month, key, + monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ]; + + while (item = data[i++]) { + date = new Date(item.day); + year = date.getFullYear(); + month = date.getMonth(); + key = monthNames[month] + " " + year + + groups[key] || (groups[key] = []); // exists OR create [] + groups[key].push(item); + } + + for (var key in groups) { + var sumVisits = groups[key].reduce(function (s, a) { + return s + a.visits; + }, 0); + + var sumSignups = groups[key].reduce(function (s, a) { + return s + a.signups; + }, 0); + + output.push({ month: key, signups: sumSignups, visits: sumVisits }) + } + + return output; + } + function onAffiliatePayments(payments) { var $table = $screen.find('table.payment-table tbody') $table.empty(); @@ -124,40 +163,46 @@ context._.each(payments.payments, function(item) { var data = {} - if(item.payment_type == 'quarterly') { + // if(item.payment_type == 'quarterly') { - if(item.quarter == 0) { - data.time = '1st Quarter ' + item.year - } - else if(item.quarter == 1) { - data.time = '2nd Quarter ' + item.year - } - else if(item.quarter == 2) { - data.time = '3rd Quarter ' + item.year - } - else if(item.quarter == 3) { - data.time = '4th Quarter ' + item.year - } + // if(item.quarter == 0) { + // data.time = '1st Quarter ' + item.year + // } + // else if(item.quarter == 1) { + // data.time = '2nd Quarter ' + item.year + // } + // else if(item.quarter == 2) { + // data.time = '3rd Quarter ' + item.year + // } + // else if(item.quarter == 3) { + // data.time = '4th Quarter ' + item.year + // } - data.sold = '' + // data.sold = '' - if(item.paid) { - data.earnings = 'PAID $' + (item.due_amount_in_cents / 100).toFixed(2); - } - else { - data.earnings = 'No earning were paid, as the $10 minimum threshold was not reached.' - } - } - else { + // if(item.paid) { + // data.earnings = 'PAID $' + (item.due_amount_in_cents / 100).toFixed(2); + // } + // else { + // data.earnings = 'No earning were paid, as the $10 minimum threshold was not reached.' + // } + // } + // else { data.time = context.JK.getMonth(item.month - 1) + ' ' + item.year; if(item.jamtracks_sold == 1) { data.sold = 'JamTracks: ' + item.jamtracks_sold + ' unit sold'; } - else { + else if(item.jamtracks_sold > 1) { data.sold = 'JamTracks: ' + item.jamtracks_sold + ' units sold'; } data.earnings = '$' + (item.due_amount_in_cents / 100).toFixed(2); - } + + if(item.subscriptions){ + data.subscriptions = $.map(item.subscriptions, function(subs){ + return '
' + getDisplayNameTier(subs.plan) + ' subscriptions - ' + subs.count + '
' + }); + } + //} var $earning = $(context._.template(template, data, {variable: 'data'})); @@ -166,50 +211,31 @@ }) } + function getDisplayNameTier(plan_code) { + var i, len, ref, subscriptionCode; + if (plan_code === '') { + plan_code = null; + } + ref = gon.global.subscription_codes; + for (i = 0, len = ref.length; i < len; i++) { + subscriptionCode = ref[i]; + if (plan_code === subscriptionCode.id) { + return subscriptionCode.name; + } + } + return "Unknown plan code=" + plan_code; + } + function updateLinks() { - var $select = $screen.find('select.link_type') - var value = $select.val() - logger.debug("value: " + value) + // affiliatePartnerData.account.id - var type = 'jamtrack_songs'; - if(value == 'JamTrack Song') { - type = 'jamtrack_songs' - } - else if(value == 'JamTrack Band') { - type = 'jamtrack_bands' - } - else if(value == 'JamTrack General') { - type = 'jamtrack_general' - } - else if(value == 'JamKazam General') { - type = 'jamkazam' - } - else if(value == 'JamKazam Session') { - type = 'sessions' - } - else if(value == 'JamKazam Recording') { - type = 'recordings' - } - else if(value == 'Custom Link') { - type = 'custom_links' - } - - $screen.find('.link-type-prompt').hide(); - $screen.find('.link-type-prompt[data-type="' + type + '"]').show(); - - if(type == 'custom_links') { - $screen.find('table.links-table').hide(); - $screen.find('.link-type-prompt[data-type="custom_links"] span.affiliate_id').text(affiliatePartnerData.account.id) - } - else { - rest.getLinks(type) - .done(populateLinkTable) - .fail(function() { - app.notify({text: 'Unable to fetch links. Please try again later.' }) - }) - } + rest.getLinks('all', affiliatePartnerData.account.id) + .done(populateLinkTable) + .fail(function() { + app.notify({text: 'Unable to fetch links. Please try again later.' }) + }) } function _renderAffiliateTab(theTab) { @@ -269,13 +295,21 @@ 'postal_code': tab_content.find('#affiliate_partner_postal_code').val(), 'country': tab_content.find('#affiliate_partner_country').val() }, - 'tax_identifier': tab_content.find('#affiliate_partner_tax_identifier').val() + 'tax_identifier': tab_content.find('#affiliate_partner_tax_identifier').val(), + 'paypal_id': tab_content.find('#affiliate_partner_paypal_id').val() } + var button = $('#affiliate-profile-account-submit') + button.addClass("disabled") rest.postAffiliatePartnerData(userId, affiliate_partner_data) - .done(postUpdateAffiliateAccountSuccess); + .done(postUpdateAffiliateAccountSuccess) + .fail(function(jqXHR) { + button.removeClass("disabled") + alert("Unable to update affiliate information.\n\n" + jqXHR.responseText) + }) } function postUpdateAffiliateAccountSuccess(response) { + $('#affiliate-profile-account-submit').removeClass("disabled") app.notify( { title: "Affiliate Account", diff --git a/web/app/assets/javascripts/support/react-components/SupportPage.js.jsx b/web/app/assets/javascripts/support/react-components/SupportPage.js.jsx index 8f0f333cb..80055c6db 100644 --- a/web/app/assets/javascripts/support/react-components/SupportPage.js.jsx +++ b/web/app/assets/javascripts/support/react-components/SupportPage.js.jsx @@ -43,7 +43,7 @@ window.SupportPage = React.createClass({ {support_warning}
The JamKazam help desk offers 1:1 help desk support only to our Gold and Platinum plan - subscribers. More information on subscription plans can be found here. If you are not a Gold or Platinum subscriber, we'd suggest that you look for help in our extensive knowledge base of help articles, diff --git a/web/app/assets/stylesheets/client/account_affiliate.scss b/web/app/assets/stylesheets/client/account_affiliate.scss index 9d52a318c..db6ad3b64 100644 --- a/web/app/assets/stylesheets/client/account_affiliate.scss +++ b/web/app/assets/stylesheets/client/account_affiliate.scss @@ -137,6 +137,8 @@ table.links-table { min-width:100%; margin-top:20px; + border-width: 1px 0 0 0; + border-style: solid; th { padding: 10px 0 20px; @@ -157,7 +159,9 @@ .url { + input { + font-family:courier; background-color: transparent; -webkit-box-shadow:none; box-shadow:none; diff --git a/web/app/assets/stylesheets/landings/affiliate_program.scss b/web/app/assets/stylesheets/landings/affiliate_program.scss index e8f936c42..f26c285c2 100644 --- a/web/app/assets/stylesheets/landings/affiliate_program.scss +++ b/web/app/assets/stylesheets/landings/affiliate_program.scss @@ -25,7 +25,7 @@ body.web.landing_page.full { } .wrapper { - padding-top:20px; + //padding-top:20px; } .row { text-align:left; diff --git a/web/app/assets/stylesheets/landings/landing_page.scss b/web/app/assets/stylesheets/landings/landing_page.scss index a644704db..cd7b4bee5 100644 --- a/web/app/assets/stylesheets/landings/landing_page.scss +++ b/web/app/assets/stylesheets/landings/landing_page.scss @@ -418,7 +418,7 @@ body.web.landing_page { font-size:14px; position: absolute; left: 60%; - top: 25px; + top: 45px; margin: 0; h1 { diff --git a/web/app/controllers/api_controller.rb b/web/app/controllers/api_controller.rb index cef52b25b..6ec166f45 100644 --- a/web/app/controllers/api_controller.rb +++ b/web/app/controllers/api_controller.rb @@ -113,7 +113,11 @@ class ApiController < ApplicationController def affiliate_partner if params[:affiliate_id] @partner = AffiliatePartner.find(params[:affiliate_id]) - if @partner.partner_user.nil? + if @partner.nil? + raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + elsif @partner.partner_user.nil? + raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + elsif !current_user.admin || @partner.partner_user != current_user raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end elsif current_user diff --git a/web/app/controllers/api_links_controller.rb b/web/app/controllers/api_links_controller.rb index 9f50d979f..8cadf984a 100644 --- a/web/app/controllers/api_links_controller.rb +++ b/web/app/controllers/api_links_controller.rb @@ -9,7 +9,27 @@ class ApiLinksController < ApiController @log || Logging.logger[ApiLinksController] end + def all + links = AffiliateLink.all.order(:created_at) + results = [] + links.each do |link| + url = nil + if link.link.index('?') + url = link.link + '&' + affiliate_params.to_query + else + url = link.link + '?' + affiliate_params.to_query + end + + results << {url: url, + target: link.name } + end + render json: results, status: 200 + end +=begin + def jamtrack_song_index + @affiliate_params = affiliate_params() + @affiliate_params = affiliate_params('jamtrack-song') results = [] @@ -106,9 +126,12 @@ class ApiLinksController < ApiController render json: results, status: 200 end +=end private - def affiliate_params(campaign) - {utm_source:'affiliate', utm_medium: 'affiliate', utm_campaign: "#{Date.today.year}-affiliate-#{campaign}", affiliate: @partner.id} + def affiliate_params() + #{utm_source:'affiliate', utm_medium: 'affiliate', utm_campaign: "#{Date.today.year}-affiliate-#{campaign}", affiliate: @partner.id}# + # the above was deemed too noisy + {affiliate: @partner.id} end end diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 6338d6ded..9c1e6bc10 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -162,6 +162,7 @@ class ApiRecurlyController < ApiController render json: {:message => x.inspect, errors: x.errors}, :status => 404 end end + def create_subscription begin sale = Sale.purchase_subscription(current_user, params[:recurly_token], params[:plan_code]) diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 46df52ac8..33bfd9388 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -865,14 +865,16 @@ class ApiUsersController < ApiController if request.post? oo.address = params[:address] oo.tax_identifier = params[:tax_identifier] + oo.paypal_id = params[:paypal_id] oo.save! - render nothing: true + render json: {}, status: 200 elsif request.get? result = {} result['account'] = { 'address' => oo.address.clone, 'tax_identifier' => oo.tax_identifier, + 'paypal_id' => oo.paypal_id, 'entity_type' => oo.entity_type, 'partner_name' => oo.partner_name, 'partner_id' => oo.partner_user_id, diff --git a/web/app/controllers/supports_controller.rb b/web/app/controllers/supports_controller.rb index 268276878..eec666d01 100644 --- a/web/app/controllers/supports_controller.rb +++ b/web/app/controllers/supports_controller.rb @@ -12,7 +12,7 @@ class SupportsController < ApplicationController gon.plan_code = current_user.subscription_plan_code @title = "Help Desk" - @description = "The JamKazam help desk offers 1:1 help desk support only to in-trial and Gold and Platinum plan subscribers." + @description = "The JamKazam help desk offers 1:1 help desk support only to our Gold and Platinum plan subscribers, as well as those in their initial 30-day trial period." render 'show', :layout => 'support' end diff --git a/web/app/views/api_affiliates/payment_show.rabl b/web/app/views/api_affiliates/payment_show.rabl index 1ac51e258..c4063798b 100644 --- a/web/app/views/api_affiliates/payment_show.rabl +++ b/web/app/views/api_affiliates/payment_show.rabl @@ -1,3 +1,9 @@ object @quarterly -attribute :closed, :paid, :due_amount_in_cents, :affiliate_partner_id, :quarter, :month, :year, :payment_type, :jamtracks_sold \ No newline at end of file +attribute :closed, :paid, :due_amount_in_cents, :affiliate_partner_id, :quarter, :month, :year, :payment_type, :jamtracks_sold + +node (:subscriptions) do |payment| + start_at = Date.new(payment.year, payment.month, 1) + end_at = Date.new(payment.year, payment.month, 1).end_of_month + AffiliateDistribution.subscription_plans_count(payment.affiliate_partner_id, start_at, end_at) +end \ No newline at end of file diff --git a/web/app/views/clients/_account_affiliate_partner.html.slim b/web/app/views/clients/_account_affiliate_partner.html.slim index 8280cf9b9..059b7f14c 100644 --- a/web/app/views/clients/_account_affiliate_partner.html.slim +++ b/web/app/views/clients/_account_affiliate_partner.html.slim @@ -41,12 +41,18 @@ script type="text/template" id="template-affiliate-partner-account" .right-col | We must have a complete mailing address and a valid tax ID in order to process and mail payments due to all affiliates.  Per the terms of the affiliate agreement, if this information is not available within 90 days of the end of a calendar quarter, then any payment obligation due to the affiliate for such calendar quarter shall be considered fully and permanently discharged, and no further payment for such calendar quarter shall be due or payable to affiliate. br + br + | For fastest receipt of payment, please specify a PayPal.Me account. + br + br | So please provide this data, and be sure to keep it current! .left-col .affiliate-label Affiliate: .w80= "{{ data.partner_name }} ({{data.entity_type}})" br + br + .affiliate-label Street Address 1: = text_field_tag('affiliate_partner_address1', "{{ data.address.address1 }}", {class: 'w60'}) br @@ -69,6 +75,18 @@ script type="text/template" id="template-affiliate-partner-account" .affiliate-label Tax ID: = text_field_tag('affiliate_partner_tax_identifier', "{{ data.tax_identifier }}", {class: 'w60'}) br + br + .affiliate-label PayPal.Me: + = text_field_tag('affiliate_partner_paypal_id', "{{ data.paypal_id }}", { class: 'w60' }) + br + span style="padding-left:138px" + | A PayPal.Me link looks like  + b paypal.me/YourName + |  Please read more info  + a href="https://www.paypal.com/us/smarthelp/article/What-is-PayPalMe-FAQ3025" + | about PayPal.Me + br + br .spacer .input-buttons .right @@ -97,55 +115,17 @@ script type="text/template" id="template-affiliate-partner-signups" table.traffic-table.jamtable thead tr - th.day DATE + th.day MONTH th.signups SIGNUPS th.visits VISITS - tbody + tbody script type="text/template" id="template-affiliate-partner-links" .links p.prompt - | This page provides you with lists of ready-to-use links you can use to direct your followers to JamKazam.  - | These links include your unique affiliate ID, and users who follow these links to JamKazam and register for an account will be  - | tagged with your affiliate ID, so that you will be paid on their purchase per the terms of the  - a.affiliate-agreement rel='#' affiliate agreement - | . You can also find a  - a.affiliate-link-page href='#' rel="external" single page listing all affiliate links - |  which you can share with others who may need to share out affiliate links on your behalf. - .link-type-section - label Link Type: - select.link_type.easydropdown name="link_type" - option value="JamTrack Song" JamTrack Song - option value="JamTrack Band" JamTrack Band - option value="JamTrack General" JamTrack General - option value="JamKazam General" JamKazam General - option value="JamKazam Session" JamKazam Session - option value="JamKazam Recording" JamKazam Recording - option value="Custom Link" Custom Link - .link-type-prompt data-type='jamtrack_songs' - | These links take users directly to the "landing page" for a JamTrack - i.e. an individual piece of music. This would be a great fit, for example, if you have produced a YouTube tutorial on how to play a particular song, or if you have music notation for a particular song, etc. - .link-type-prompt data-type='jamtrack_bands' - | These links take users directly to the "landing page" for a band to promote all the JamTracks by that band, not just an individual song. This would be a great fit, for example, if you have music notation for several songs by a band, etc. - .link-type-prompt data-type='jamtrack_general' - | These links take users directly to a"landing page" that promotes JamTracks in general. This page will feature the song listed in the link as an example of a JamTrack, but the landing page is built to promote JamTracks in general versus promoting just the one song. This is a good fit if you want to let your followers know about JamTracks in general. - .link-type-prompt data-type='jamkazam' - | These links take users directly to a product page that promotes JamKazam more generally. This is a good fit if you want to let your followers know about JamKazam or its products in general. - .link-type-prompt data-type='sessions' - | These links take users to a page where they can listen to a session you have organized, and in which you are scheduled to perform, either alone or with others. This is a good fit if you can perform either solo or in a group with other musicians in a JamKazam session, and draw an audience to listen to your session. During the session, you can recommend that your audience members sign up for JamKazam from this page. Below is a list of your currently scheduled JamKazam sessions for which you may share links. - .link-type-prompt data-type='recordings' - | These links take users to a page with a recording you have made in a JamKazam session. This is a good fit if you have made a nice recording in a JamKazam session and can share it with your followers via Facebook, Twitter, email, and so on. Below is a list of your most recent JamKazam recordings for which you may share links. - .link-type-prompt data-type='custom_links' - p You may also link to any page on the JamKazam website and append the following text to the URL of the page to which you are linking: - - p.example-link - | ?utm_source=affiliate&utm_medium=affiliate&utm_campaign=2015-affiliate-custom&affiliate= - span.affiliate_id - - p For example, if you were linking to the JamKazam home page, you would combine https://www.jamkazam.com with the text above, and the result would be: - - p.example-link - | https://www.jamkazam.com?utm_source=affiliate&utm_medium=affiliate&utm_campaign=2015-affiliate-custom&affiliate= - span.affiliate_id + | This page provides you with a list of ready-to-use links you can share with your followers to let them know about JamKazam. These links include your unique affiliate ID, and users who follow these links to JamKazam and sign up for a JamKazam account will be tagged with your affiliate ID, so that when they purchase a subscription and/or JamTracks, you will be paid a revenue share on their purchase payments per the terms of the affiliate agreement. + p.prompt + | Click “select link” next to the link you want to use, then copy the link to the clipboard and paste it wherever you want to share it. table.links-table thead @@ -153,12 +133,12 @@ script type="text/template" id="template-affiliate-partner-links" th.target TARGET PAGE th.copy-link th.url URL - tbody + tbody script type="text/template" id="template-affiliate-partner-signups-row" tr - td.day - | {{data.day}} + td.month + | {{data.month}} td.signups | {{data.signups}} td.visits @@ -169,9 +149,10 @@ script type="text/template" id="template-affiliate-partner-earnings" thead tr th.month MONTH - th.sales SALES + th.sales JAMTRACKS + th.subscriptions SUBSCRIPTIONS th.earnings AFFILIATE EARNINGS - tbody + tbody script type="text/template" id="template-affiliate-partner-earnings-row" tr data-type="{{data.payment_type}}" @@ -179,6 +160,8 @@ script type="text/template" id="template-affiliate-partner-earnings-row" | {{data.time}} td.sales | {{data.sold}} + td.subscriptions + | {{ data.subscriptions }} td.earnings | {{data.earnings}} @@ -190,4 +173,4 @@ script type="text/template" id="template-affiliate-link-entry" td.copy-link a href='#' select link td.url - input type="text" value="{{data.url}}" \ No newline at end of file + input type="text" value="{{data.url}}" diff --git a/web/app/views/landings/affiliate_program.html.slim b/web/app/views/landings/affiliate_program.html.slim index 728c40385..29ecbfec0 100644 --- a/web/app/views/landings/affiliate_program.html.slim +++ b/web/app/views/landings/affiliate_program.html.slim @@ -2,18 +2,14 @@ - provide(:description, 'Signup for JamKazam Affiliate Program') .row - .column - h1.product-headline JamKazam Affiliate Program - p Do you have a following of musicians on Facebook, YouTube, Twitter or an email list? Generate income simply by letting them know about JamKazam. - p Let's say you make YouTube tutorial videos. You can link directly from a video on how to play "Back in Black" to our JamTrack for that song. Video watchers can get this first JamTrack free, and can buy others if they like. You get paid every time they buy something from us for 2 years. - p Or let's say you have a Facebook group for guitarist with 5,000 members. You can let them know they can play together free on JamKazam. For everyone who signs up, you get paid every time they buy something from us for 2 years. - p You don't have to sell anything. Just let your followers know about cool new stuff they'll like! To get started, simply review the affiliate agreement below, accept it (at the end of the agreement), and then start sharing links with your affiliate code. When referred users buy JamTracks, JamBlasters, JamLessons, and so on, you get paid! - .column - h1 Learn How to Make Money by Referring Users - - if !Rails.env.test? - .video-wrapper - .video-container - iframe src="//www.youtube.com/embed/96YTnO_H9a4" frameborder="0" allowfullscreen + h1 JamKazam Affiliate Program + p Do you have an audience of musician followers on social media like Facebook, YouTube, Instagram, or Twitter - or on your website, email list, etc? + br + p Most of the musicians in the world don't know JamKazam exists. Now you can let your audience know about JamKazam - which is something they'll be very excited to hear about - and also generate recurring income from any of your referrals that purchase JamKazam premium subscriptions and/or JamTracks. + br + p JamKazam provides a 30-day free trial that makes it fun and risk-free for anyone to check out the platform. After that, users can continue with a free plan or purchase an upgrade to a premium subscription, with plans available at $4.99, $9.99, or $19.99 per month. JamKazam will pay you 30% of these revenues for a period of 3 years from the signup date of each of your referred users. + br + p You don't sell anything. Just share the word about a service you really like and that most others haven't heard of yet. To get started, review the affiliate agreement details below and sign up as an affiliate at the bottom of this page. Then you can go to the Account/Affiliate page in our app or website to get special links tagged with your affiliate ID that you can share with your audience. You can also track signups and earnings from that page. br clear="all" .row h1 JamKazam Affiliate Agreement @@ -45,8 +41,9 @@ option value="Other" Other .agree-disagree-buttons - = link_to image_tag("content/agree_button.png", {:width => 213, :height => 50 }), '#', class: "agree-button" - = link_to image_tag("content/disagree_button.png", {:width => 213, :height => 50 }), '#', class: "disagree-button" + + = link_to "I Agree", '#', class: "agree-button button-orange" + = link_to "I Disagree", '#', class: "disagree-button button-grey" p.disagree-text.hidden | Thank you for your interest in the JamKazam affiliate program. We are sorry, but you cannot join the program without consenting to the terms of this Agreement. diff --git a/web/app/views/legal/_partner_agreement_v1.html.erb b/web/app/views/legal/_partner_agreement_v1.html.erb index 1dbc81472..293c9d869 100644 --- a/web/app/views/legal/_partner_agreement_v1.html.erb +++ b/web/app/views/legal/_partner_agreement_v1.html.erb @@ -1,7 +1,7 @@
-

Updated: April 30, 2015.

+

Updated: February 9, 2021.

@@ -23,7 +23,7 @@

The purpose of the Program is to permit you to advertise Products on Your Site and to earn advertising fees for Qualifying Purchases (defined in Section 7) made by your Qualifying Customers (defined in Section 7). A “Product” - is an item sold on the JamKazam Site and listed in the Program Advertising Fee Schedule in Section 10. In order to facilitate your advertisement of Products, we may make available to you data, images, text, link formats, widgets, links, and other linking tools, and other information in connection with the Program (“Content”). + a product or service sold on the JamKazam Site and listed in the Program Advertising Fee Schedule in Section 10. In order to facilitate your advertisement of Products, we may make available to you data, images, text, link formats, widgets, links, and other linking tools, and other information in connection with the Program (“Content”).

@@ -157,7 +157,7 @@

We will pay you advertising fees on Qualifying Purchases in accordance with Section 8 and the Program Advertising Fee Schedule in Section 10. Subject to the exclusions set forth below, a “Qualifying Purchase” - occurs when a Qualifying Customer: (a) purchases a Product within two (2) years of the date on which such Qualifying Customer registered to create his/her JamKazam account; and (b) pays for such Product. A “Qualifying Customer” + occurs when a Qualifying Customer purchases and pays for a Product within three (3) years of the date on which such Qualifying Customer registered to create his/her JamKazam account. A “Qualifying Customer” is an end user who: (a) clicks through a Special Link on Your Site to the JamKazam Site; and (b) during the single Session created by this click through, registers to create a new JamKazam account. A “Session” begins when an end user clicks through a Special Link on Your Site to the JamKazam Site and ends when such end user leaves the JamKazam Site.

@@ -192,7 +192,7 @@

- We will pay you advertising fees on a quarterly basis for Qualifying Purchases downloaded, shipped, or otherwise fulfilled (as applicable) in a given calendar quarter, subject to any applicable withholding or deduction described below. We will pay you approximately 30 days following the end of each calendar quarter by mailing a check in the amount of the advertising fees you earn to the mailing address then-currently associated with your JamKazam account, but we may accrue and withhold payment of advertising fees until the total amount due to you is at least US$50.00. If you do not have a valid mailing address associated with your JamKazam account within 30 days of the end of a calendar quarter, we will withhold any unpaid accrued advertising fees until you have associated a valid mailing address and notified us that you have done so. + We will pay you advertising fees on a quarterly basis for Qualifying Purchases paid for in a given calendar quarter, subject to any applicable withholding or deduction described below. We will pay you approximately 30 days following the end of each calendar quarter by mailing a check in the amount of the advertising fees you earn to the mailing address then-currently associated with your JamKazam account, or by or processing a digital funds transfer (e.g. PayPal) to an account you designate, but we may accrue and withhold payment of advertising fees until the total amount due to you is at least US$25.00. If you do not have a valid mailing address associated with your JamKazam account within 30 days of the end of a calendar quarter, we will withhold any unpaid accrued advertising fees until you have associated a valid mailing address and notified us that you have done so.

@@ -218,11 +218,32 @@

- We will determine and calculate amounts payable to you as advertising fees for Qualifying Purchases as set forth in the table below (the “Program Advertising Fee Schedule”). + We will determine and calculate amounts payable to you as advertising fees for Qualifying Purchases as set forth below (the “Program Advertising Fee Schedule”).

+ + + + + + <%#

+ @@ -236,11 +257,12 @@

-
+ %> +

- From time to time, we may modify this Program Advertising Fee Schedule as part of modifications made to this Agreement. + From time to time, we may modify this Program Advertising Fee Schedule as part of modifications made to this Agreement.

@@ -351,7 +373,7 @@

- To begin an arbitration proceeding, you must send a letter requesting arbitration and describing your claim to us at: JamKazam, Inc., Attn: Legal Department, 5813 Lookout Mountain Drive, Austin TX 78731. The arbitration will be conducted by the American Arbitration Association (“AAA”) under its rules, including the AAA’s Supplementary Procedures for Consumer-Related Disputes. The AAA’s rules are available at www.adr.org or by calling 1-800-778-7879. Payment of all filing, administration and arbitrator fees will be governed by the AAA’s rules. We will reimburse those fees for claims totaling less than $10,000 unless the arbitrator determines the claims are frivolous. Likewise, we will not seek attorneys’ + To begin an arbitration proceeding, you must send a letter requesting arbitration and describing your claim to us at: JamKazam, Inc., Attn: Legal Department, 3924 Knollwood Drive, Austin TX 78731. The arbitration will be conducted by the American Arbitration Association (“AAA”) under its rules, including the AAA’s Supplementary Procedures for Consumer-Related Disputes. The AAA’s rules are available at www.adr.org or by calling 1-800-778-7879. Payment of all filing, administration and arbitrator fees will be governed by the AAA’s rules. We will reimburse those fees for claims totaling less than $10,000 unless the arbitrator determines the claims are frivolous. Likewise, we will not seek attorneys’ fees and costs in arbitration unless the arbitrator determines the claims are frivolous. You may choose to have the arbitration conducted by telephone, based on written submissions, or in person in the county where you live or at another mutually agreed location.

@@ -417,6 +439,6 @@ -->

- JamKazam Confidential                4/17/2015 + JamKazam Confidential                02/09/2021

\ No newline at end of file diff --git a/web/bin/test b/web/bin/test index 025517a7b..b99e7eeb3 100755 --- a/web/bin/test +++ b/web/bin/test @@ -5,6 +5,10 @@ tests=( "spec/features/signup_spec.rb" "spec/features/signin_spec.rb" + "spec/features/affiliate_program_spec.rb" + "spec/features/affiliate_visit_tracking_spec.rb" + "spec/features/affiliate_referral_spec.rb" + "spec/controllers/api_affiliate_controller_spec.rb" ) diff --git a/web/config/routes.rb b/web/config/routes.rb index eb0e323ab..8e08735c0 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -759,12 +759,13 @@ Rails.application.routes.draw do match '/live_streams/bad_audio' => 'api_alerts#bad_audio', :via => :post # used by client; don't change route # links generated to help affiliates share relevant links - get '/links/jamtrack_songs' => 'api_links#jamtrack_song_index' - get '/links/jamtrack_bands' => 'api_links#jamtrack_band_index' - get '/links/jamtrack_general' => 'api_links#jamtrack_general_index' - get '/links/jamkazam' => 'api_links#jamkazam_general_index' - get '/links/sessions' => 'api_links#session_index' - get '/links/recordings' => 'api_links#recording_index' + #get '/links/jamtrack_songs' => 'api_links#jamtrack_song_index' + #get '/links/jamtrack_bands' => 'api_links#jamtrack_band_index' + #get '/links/jamtrack_general' => 'api_links#jamtrack_general_index' + #get '/links/jamkazam' => 'api_links#jamkazam_general_index' + #get '/links/sessions' => 'api_links#session_index' + #get '/links/recordings' => 'api_links#recording_index' + get '/links/all' => 'api_links#all' match '/lesson_sessions' => 'api_lesson_sessions#index', :via => :get match '/lesson_bookings/unprocessed' => 'api_lesson_bookings#unprocessed', :via => :get diff --git a/web/migrate.sh b/web/migrate.sh index a9afa1578..01e0cd2eb 100755 --- a/web/migrate.sh +++ b/web/migrate.sh @@ -1,2 +1,2 @@ #!/bin/bash -bundle exec jam_db up --connopts=dbname:jam host:localhost user:postgres password:postgres --verbose +RAILS_ENV=development bundle exec rake db:jam_ruby:migrate diff --git a/web/spec/controllers/api_affiliate_controller_spec.rb b/web/spec/controllers/api_affiliate_controller_spec.rb index 00f40c413..2475d2058 100644 --- a/web/spec/controllers/api_affiliate_controller_spec.rb +++ b/web/spec/controllers/api_affiliate_controller_spec.rb @@ -79,4 +79,37 @@ describe ApiAffiliateController, type: :controller do end end + describe "payment_index" do + it "empty" do + get :payment_index + response.should be_success + JSON.parse(response.body)['payments'].should eq([]) + end + + it "presents single JamTrack item" do + FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner: partner1, closed: true, due_amount_in_cents: 20, month: 1, year: 2015, jamtracks_sold: 1) + + get :payment_index + response.should be_success + JSON.parse(response.body)['payments'].should eq([{"closed" => true, "paid" => nil, "payment_type" => "monthly", "quarter" => nil, "month" => 1, "year" => 2015, "due_amount_in_cents" => 20, "affiliate_partner_id" => partner1.id, "jamtracks_sold" => 1, "subscriptions" => [] }]) + end + + it "presents subscriptions" do + #Silver plan subscription on January + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubsilver', + affiliate_referral: partner1, + affiliate_referral_fee_in_cents: 15, + created_at: Date.new(2015, 1, 1) + ) + AffiliatePartner.tally_up(Date.new(2015, 1, 1)) + + get :payment_index + response.should be_success + expect(JSON.parse(response.body)['payments']).to have(6).things + expect(JSON.parse(response.body)['payments']).to include({"closed" => false, "paid" => nil, "payment_type" => "monthly", "quarter" => nil, "month" => 1, "year" => 2015, "due_amount_in_cents" => 15, "affiliate_partner_id" => partner1.id, "jamtracks_sold" => 0, "subscriptions" => [{ "plan" => "jamsubsilver", "count" => 1 }] }) + end + end + end diff --git a/web/spec/factories.rb b/web/spec/factories.rb index 464528e49..3ed2fd869 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -863,6 +863,10 @@ FactoryGirl.define do legalese Faker::Lorem.paragraphs(6).join("\n\n") end + factory :affiliate_distribution, class: 'JamRuby::AffiliateDistribution' do + association :affiliate_referral, factory: :affiliate_partner + end + factory :gift_card, class: 'JamRuby::GiftCard' do sequence(:code) {|n| n.to_s} card_type GiftCard::JAM_TRACKS_5 diff --git a/web/spec/features/account_affiliate_spec.rb b/web/spec/features/account_affiliate_spec.rb index 9747a373b..a66f953de 100644 --- a/web/spec/features/account_affiliate_spec.rb +++ b/web/spec/features/account_affiliate_spec.rb @@ -6,7 +6,9 @@ describe "Account Affiliate", :js => true, :type => :feature, :capybara_feature let(:user) {FactoryGirl.create(:user)} let(:partner) { FactoryGirl.create(:affiliate_partner) } + let(:user_partner) { FactoryGirl.create(:user, affiliate_referral: partner) } let(:jam_track) {FactoryGirl.create(:jam_track)} + let(:sale) {Sale.create_jam_track_sale(user_partner)} before(:each) do JamTrackRight.delete_all @@ -14,6 +16,7 @@ describe "Account Affiliate", :js => true, :type => :feature, :capybara_feature AffiliateQuarterlyPayment.delete_all AffiliateMonthlyPayment.delete_all AffiliateTrafficTotal.delete_all + AffiliateDistribution.delete_all UserMailer.deliveries.clear emulate_client end @@ -44,109 +47,314 @@ describe "Account Affiliate", :js => true, :type => :feature, :capybara_feature end it "works on no data" do - jam_track.touch - + visit "/client#/account/affiliatePartner" find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') # take a look at the links tab - find('a#affiliate-partner-links-link').trigger(:click) - - find('#account-affiliate-partner tr td.target') + find('a#affiliate-partner-links-link').click + #find('#account-affiliate-partner tr td.target') + find('table.links-table') # can't find this on the page for some reason: #jk_select('Custom Link', '#account-affiliate-partner select.link_type') #find('.link-type-prompt[data-type="custom_links"]') - find('a#affiliate-partner-signups-link').trigger(:click) + find('a#affiliate-partner-signups-link').click find('table.traffic-table') - find('a#affiliate-partner-earnings-link').trigger(:click) + find('a#affiliate-partner-earnings-link').click find('table.payment-table') - find('a#affiliate-partner-agreement-link').trigger(:click) + find('a#affiliate-partner-agreement-link').click find('h2', text: 'JamKazam Affiliate Agreement') - find('span.c0', text: 'Updated: April 30, 2015') + find('span.c0', text: 'Updated: February 9, 2021.') + + #save_screenshot("account_affiliate_agreement.png") end - it "shows data" do + it "shows signups data" do + visit "/client#/account/affiliatePartner" find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') # verify traffic data shows correctly - day1 = Date.parse('2015-04-05') - FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day1, signups: 1, visits:2) - find('a#affiliate-partner-signups-link').trigger(:click) - find('table.traffic-table tr td.day', text: "April 5") - find('table.traffic-table tr td.signups', text: '1') - find('table.traffic-table tr td.visits', text: '2') + day1 = Date.parse('2015-01-01') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day1, signups: 1, visits: 5) + day2 = Date.parse('2015-01-15') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day2, signups: 5, visits: 10) + day3 = Date.parse('2015-01-31') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day3, signups: 10, visits: 20) + day4 = Date.parse('2015-02-01') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day4, signups: 1, visits: 2) + + day5 = Date.parse('2015-03-15') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day5, signups: 10, visits: 20) - find('a#affiliate-partner-earnings-link').trigger(:click) + day6 = Date.parse('2015-04-01') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day6, signups: 20, visits: 50) + + day7 = Date.parse('2015-04-05') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day7, signups: 5, visits: 10) + + find('a#affiliate-partner-signups-link').click + + months = page.all('table.traffic-table tr td.month') + signups = page.all('table.traffic-table tr td.signups') + visits = page.all('table.traffic-table tr td.visits') + + months[0].should have_content("April") + months[1].should have_content("March") + months[2].should have_content("February") + months[3].should have_content("January") + + signups[0].should have_content("25") + signups[1].should have_content("10") + signups[2].should have_content("1") + signups[3].should have_content("16") + + visits[0].should have_content("60") + visits[1].should have_content("20") + visits[2].should have_content("2") + visits[3].should have_content("35") + + find('a#affiliate-partner-earnings-link').click find('table.payment-table') - day2 = Date.parse('2015-04-07') - FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day2, signups: 3, visits:4) - find('a#affiliate-partner-signups-link').trigger(:click) - find('table.traffic-table tr td.day', text: "April 7") - find('table.traffic-table tr td.signups', text: '3') - find('table.traffic-table tr td.visits', text: '4') + day8 = Date.parse('2015-04-07') + FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day8, signups: 5, visits: 10) + + find('a#affiliate-partner-signups-link').click - # verify earnings data correctly - FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:1, due_amount_in_cents:20, jamtracks_sold: 1, closed:true) + months = page.all('table.traffic-table tr td.month') + signups = page.all('table.traffic-table tr td.signups') + visits = page.all('table.traffic-table tr td.visits') - find('a#affiliate-partner-earnings-link').trigger(:click) - find('table.payment-table tr td.month', text: "January 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') - find('table.payment-table tr td.earnings', text: '$0.20') + months[0].should have_content("April") + signups[0].should have_content("30") + visits[0].should have_content("70") + #save_screenshot("account_affiliate_signup_links.png") - find('a#affiliate-partner-signups-link').trigger(:click) - find('table.traffic-table') - - FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:2, due_amount_in_cents:40, jamtracks_sold: 2, closed:true) - FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:3, due_amount_in_cents:60, jamtracks_sold: 3, closed:true) - quarter1 = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner, year:2015, quarter:0, due_amount_in_cents:120, jamtracks_sold: 6, closed:true, paid:false) - FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:4, due_amount_in_cents:2000, jamtracks_sold: 100, closed:true) - - find('a#affiliate-partner-earnings-link').trigger(:click) - find('table.payment-table tr td.month', text: "January 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') - find('table.payment-table tr td.earnings', text: '$0.20') - find('table.payment-table tr td.month', text: "February 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 2 units sold') - find('table.payment-table tr td.earnings', text: '$0.40') - find('table.payment-table tr td.month', text: "March 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 3 units sold') - find('table.payment-table tr td.earnings', text: '$0.60') - find('table.payment-table tr td.month', text: "1st Quarter 2015") - find('table.payment-table tr td.earnings', text: 'No earning were paid, as the $10 minimum threshold was not reached.') - - - - find('a#affiliate-partner-signups-link').trigger(:click) - find('table.traffic-table') - - quarter1.paid = true - quarter1.save! - FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner, year:2015, quarter:1, due_amount_in_cents:2000, jamtracks_sold: 100, closed:true, paid:true) - - find('a#affiliate-partner-earnings-link').trigger(:click) - find('table.payment-table tr td.month', text: "January 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') - find('table.payment-table tr td.earnings', text: '$0.20') - find('table.payment-table tr td.month', text: "February 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 2 units sold') - find('table.payment-table tr td.earnings', text: '$0.40') - find('table.payment-table tr td.month', text: "March 2015") - find('table.payment-table tr td.sales', text: 'JamTracks: 3 units sold') - find('table.payment-table tr td.earnings', text: '$0.60') - - find('table.payment-table tr td.month', text: "1st Quarter 2015") - find('table.payment-table tr td.earnings', text: 'PAID $1.20') - find('table.payment-table tr td.month', text: "2nd Quarter 2015") - find('table.payment-table tr td.earnings', text: 'PAID $20.00') end + + it "shows earnings" do + jam_track.touch + + visit "/client#/account/affiliatePartner" + find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') + + # verify earnings data correctly + FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:1, due_amount_in_cents:25, jamtracks_sold: 1, closed:true) + + find('a#affiliate-partner-earnings-link').click + find('table.payment-table tr td.month', text: "January 2015") + find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') + find('table.payment-table tr td.earnings', text: '$0.25') + + find('a#affiliate-partner-signups-link').click + find('table.traffic-table') + + FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:2, due_amount_in_cents:50, jamtracks_sold: 2, closed:true) + FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:3, due_amount_in_cents:75, jamtracks_sold: 3, closed:true) + FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:4, due_amount_in_cents:0, jamtracks_sold: 0, closed:true) + + + find('a#affiliate-partner-earnings-link').click + + + months = page.all("table.payment-table tr td.month") + sales = page.all("table.payment-table tr td.sales") + earnings = page.all("table.payment-table tr td.earnings") + + months[0].should have_content("April 2015") + months[1].should have_content("March 2015") + months[2].should have_content("February 2015") + months[3].should have_content("January 2015") + + sales[0].should have_content("") + sales[1].should have_content("JamTracks: 3 units sold") + sales[2].should have_content("JamTracks: 2 units sold") + sales[3].should have_content("JamTracks: 1 unit sold") + + earnings[0].should have_content("") + earnings[1].should have_content("$0.75") + earnings[2].should have_content("$0.50") + earnings[3].should have_content("$0.25") + + #save_screenshot("account_affiliate_earnings.png") + end + + it "shows earnings by Subscription and JamTrack sales" do + #Silver plan subscription on January + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubsilver', + affiliate_referral: partner, + affiliate_referral_fee_in_cents: 15, + created_at: Date.new(2015, 1, 1) + ) + + #JamTrack sale on January + jam_track.reload + shopping_cart = ShoppingCart.create user_partner, jam_track, 1, false + real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil) + real_sale.affiliate_referral_fee_in_cents.should eq(25) + real_sale.created_at = Date.new(2015, 1, 1) + real_sale.save! + real_sale.affiliate_distributions.first.created_at = real_sale.created_at + real_sale.affiliate_distributions.first.save! + + + #Gold plan subscription on January + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubgold', + affiliate_referral: partner, + affiliate_referral_fee_in_cents: 30, + created_at: Date.new(2015, 1, 1) + ) + + #Platinum plan subscription in February + FactoryGirl.create(:affiliate_distribution, + product_type: 'Subscription', + product_code: 'jamsubplatinum', + affiliate_referral: partner, + affiliate_referral_fee_in_cents: 60, + created_at: Date.new(2015, 2, 1) + ) + + AffiliatePartner.tally_up(Date.new(2015, 2, 1)) + + visit "/client#/account/affiliatePartner" + find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') + + find('a#affiliate-partner-earnings-link').click + + # within('table.payment-table') do + # find('tr td.month', text: "January 2015") + # find('tr td.sales', text: 'JamTracks: 1 unit sold') + # find('tr td.subscriptions', text: 'Gold subscriptions - 1') + # find('tr td.subscriptions', text: 'Silver subscriptions - 1') + # find('tr td.earnings', text: '$1.70') + # end + + months = page.all("table.payment-table tbody tr td.month") + months[0].should have_content("March 2015") + months[1].should have_content("February 2015") + months[2].should have_content("January 2015") + + sales = page.all("table.payment-table tbody tr td.sales") + sales[0].should have_content("JamTracks: 0 units sold") + sales[1].should have_content("JamTracks: 0 units sold") + sales[2].should have_content("JamTracks: 1 unit sold") + + subscriptions = page.all("table.payment-table tbody tr td.subscriptions") + subscriptions[0].should have_content("") + subscriptions[1].should have_content("") + subscriptions[2].should have_content("Gold subscriptions - 1") + subscriptions[2].should have_content("Silver subscriptions - 1") + + earnings = page.all("table.payment-table tbody tr td.earnings") + earnings[0].should have_content("") + earnings[1].should have_content("$0.60") + earnings[2].should have_content("$0.70") + + #save_screenshot("account_affiliate_earnings_with_subscriptions.png") + + end + + + # it "shows data" do + # sign_in_poltergeist partner.partner_user + + # visit "/client#/account/affiliatePartner" + # find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') + + # # verify traffic data shows correctly + # day1 = Date.parse('2015-04-05') + # FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day1, signups: 1, visits:2) + + # #find('a#affiliate-partner-signups-link').trigger(:click) + # find('a#affiliate-partner-signups-link').click + # find('table.traffic-table tr td.month', text: "April 2015") + # find('table.traffic-table tr td.signups', text: '1') + # find('table.traffic-table tr td.visits', text: '2') + + + # #find('a#affiliate-partner-earnings-link').trigger(:click) + # find('a#affiliate-partner-earnings-link').click + # find('table.payment-table') + + # day2 = Date.parse('2015-04-07') + # FactoryGirl.create(:affiliate_traffic_total, affiliate_partner: partner, day: day2, signups: 3, visits:4) + # #find('a#affiliate-partner-signups-link').trigger(:click) + # find('a#affiliate-partner-signups-link').click + # find('table.traffic-table tr td.month', text: "April 2015") + # find('table.traffic-table tr td.signups', text: '4') + # find('table.traffic-table tr td.visits', text: '6') + + # # verify earnings data correctly + # FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:1, due_amount_in_cents:20, jamtracks_sold: 1, closed:true) + + # #find('a#affiliate-partner-earnings-link').trigger(:click) + # find('a#affiliate-partner-earnings-link').click + # find('table.payment-table tr td.month', text: "January 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') + # find('table.payment-table tr td.earnings', text: '$0.20') + + + # #find('a#affiliate-partner-signups-link').trigger(:click) + # find('a#affiliate-partner-signups-link').click + # find('table.traffic-table') + + # FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:2, due_amount_in_cents:40, jamtracks_sold: 2, closed:true) + # FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:3, due_amount_in_cents:60, jamtracks_sold: 3, closed:true) + # quarter1 = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner, year:2015, quarter:0, due_amount_in_cents:120, jamtracks_sold: 6, closed:true, paid:false) + # FactoryGirl.create(:affiliate_monthly_payment, affiliate_partner:partner, year:2015, month:4, due_amount_in_cents:2000, jamtracks_sold: 100, closed:true) + + # #find('a#affiliate-partner-earnings-link').trigger(:click) + # find('a#affiliate-partner-earnings-link').click + # find('table.payment-table tr td.month', text: "January 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') + # find('table.payment-table tr td.earnings', text: '$0.20') + # find('table.payment-table tr td.month', text: "February 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 2 units sold') + # find('table.payment-table tr td.earnings', text: '$0.40') + # find('table.payment-table tr td.month', text: "March 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 3 units sold') + # find('table.payment-table tr td.earnings', text: '$0.60') + # find('table.payment-table tr td.month', text: "1st Quarter 2015") + # find('table.payment-table tr td.earnings', text: 'No earning were paid, as the $10 minimum threshold was not reached.') + + + + # #find('a#affiliate-partner-signups-link').trigger(:click) + # find('a#affiliate-partner-signups-link').click + # find('table.traffic-table') + + # quarter1.paid = true + # quarter1.save! + # FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner, year:2015, quarter:1, due_amount_in_cents:2000, jamtracks_sold: 100, closed:true, paid:true) + + # #find('a#affiliate-partner-earnings-link').trigger(:click) + # find('a#affiliate-partner-earnings-link').click + # find('table.payment-table tr td.month', text: "January 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 1 unit sold') + # find('table.payment-table tr td.earnings', text: '$0.20') + # find('table.payment-table tr td.month', text: "February 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 2 units sold') + # find('table.payment-table tr td.earnings', text: '$0.40') + # find('table.payment-table tr td.month', text: "March 2015") + # find('table.payment-table tr td.sales', text: 'JamTracks: 3 units sold') + # find('table.payment-table tr td.earnings', text: '$0.60') + + # find('table.payment-table tr td.month', text: "1st Quarter 2015") + # find('table.payment-table tr td.earnings', text: 'PAID $1.20') + # find('table.payment-table tr td.month', text: "2nd Quarter 2015") + # find('table.payment-table tr td.earnings', text: 'PAID $20.00') + # end end end diff --git a/web/spec/features/affiliate_program_spec.rb b/web/spec/features/affiliate_program_spec.rb index 26378cd92..ea4edeeae 100644 --- a/web/spec/features/affiliate_program_spec.rb +++ b/web/spec/features/affiliate_program_spec.rb @@ -7,7 +7,6 @@ describe "Affiliate Program", :js => true, :type => :feature, :capybara_feature let(:user) { FactoryGirl.create(:user) } before(:each) do - User.delete_all AffiliateQuarterlyPayment.delete_all AffiliateMonthlyPayment.delete_all AffiliateTrafficTotal.delete_all @@ -27,16 +26,16 @@ describe "Affiliate Program", :js => true, :type => :feature, :capybara_feature it "logged in user creates affiliate" do fast_signin user, '/affiliateProgram' - find('input#entity_individual').trigger(:click) + find('input#entity_individual').click - find('.agree-button').trigger(:click) + find('.agree-button').click find('h1', text: 'congratulations') - find('.button-orange', text: 'GO TO AFFILIATE PAGE').trigger(:click) + find('.button-orange', text: 'GO TO AFFILIATE PAGE').click find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') - partner = AffiliatePartner.first + partner = AffiliatePartner.order('created_at desc').first partner.partner_user.should eq(user) partner.entity_type.should eq('Individual') end @@ -44,18 +43,18 @@ describe "Affiliate Program", :js => true, :type => :feature, :capybara_feature it "logged in user creates entity affiliate" do fast_signin user, '/affiliateProgram' - find('input#entity_entity').trigger(:click) + find('input#entity_entity').click fill_in('entity-name', with: 'Mr. Bubbles') select('Sole Proprietor', from:'entity-type') - find('.agree-button').trigger(:click) + find('.agree-button').click find('h1', text: 'congratulations') - find('.button-orange', text: 'GO TO AFFILIATE PAGE').trigger(:click) + find('.button-orange', text: 'GO TO AFFILIATE PAGE').click find('.tab-account', text: 'So please provide this data, and be sure to keep it current!') - partner = AffiliatePartner.first + partner = AffiliatePartner.order('created_at desc').first partner.partner_user.should eq(user) partner.entity_type.should eq('Sole Proprietor') end @@ -63,26 +62,25 @@ describe "Affiliate Program", :js => true, :type => :feature, :capybara_feature it "new user creates individual affiliate" do visit '/affiliateProgram' - find('input#entity_individual').trigger(:click) + find('input#entity_individual').click - find('.agree-button').trigger(:click) + find('.agree-button').click find('h1', text: 'congratulations') - find('.button-orange', text: 'GO SIGNUP').trigger(:click) + find('.button-orange', text: 'GO SIGNUP').click fill_in "jam_ruby_user[first_name]", with: "Affiliate1" fill_in "jam_ruby_user[last_name]", with: "Someone" fill_in "jam_ruby_user[email]", with: "affiliate1@jamkazam.com" fill_in "jam_ruby_user[password]", with: "jam123" fill_in "jam_ruby_user[password_confirmation]", with: "jam123" - check("jam_ruby_user[instruments][drums][selected]") check("jam_ruby_user[terms_of_service]") click_button "CREATE ACCOUNT" should have_title("JamKazam | Congratulations") - found_user = User.first - partner = AffiliatePartner.first + found_user = User.order('created_at desc').first + partner = AffiliatePartner.order('created_at desc').first partner.partner_user.should eq(found_user) partner.entity_type.should eq('Individual') end diff --git a/web/spec/features/affiliate_referral_spec.rb b/web/spec/features/affiliate_referral_spec.rb index c7d8b7854..3486acc63 100644 --- a/web/spec/features/affiliate_referral_spec.rb +++ b/web/spec/features/affiliate_referral_spec.rb @@ -39,7 +39,6 @@ describe "affiliate visit tracking", :js => true, :type => :feature, :capybara_ fill_in "jam_ruby_user[email]", with: "referral1@jamkazam.com" fill_in "jam_ruby_user[password]", with: "jam123" fill_in "jam_ruby_user[password_confirmation]", with: "jam123" - check("jam_ruby_user[instruments][drums][selected]") check("jam_ruby_user[terms_of_service]") click_button "CREATE ACCOUNT" diff --git a/web/spec/features/affiliate_visit_tracking.rb b/web/spec/features/affiliate_visit_tracking.rb deleted file mode 100644 index 0afe3e131..000000000 --- a/web/spec/features/affiliate_visit_tracking.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -describe "affiliate visit tracking" do - - subject { page } - - let(:user) { FactoryGirl.create(:user) } - let(:partner) { FactoryGirl.create(:affiliate_partner) } - let(:affiliate_params) { partner.affiliate_query_params } - - before(:each) do - AffiliateReferralVisit.delete_all - end - - it "tracks" do - visit '/?' + affiliate_params - - should_be_at_root - AffiliateReferralVisit.count.should eq(1) - visit = AffiliateReferralVisit.first - visit.visited_url.should eq('/?' + affiliate_params) - visit.affiliate_partner_id.should eq(partner.id) - visit.first_visit.should be true - - download_url = '/downloads?' + affiliate_params - visit download_url - find('h2.create-account-header') - - - AffiliateReferralVisit.count.should eq(2) - visit = AffiliateReferralVisit.find_by_visited_url(download_url) - visit.affiliate_partner_id.should eq(partner.id) - visit.first_visit.should be false - end - -end diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb index c5007e977..b4a869629 100644 --- a/web/spec/spec_helper.rb +++ b/web/spec/spec_helper.rb @@ -165,6 +165,14 @@ Capybara.server = :puma config.visible_text_only = true end + Capybara.register_driver :jamkazam do |app| + require 'selenium/webdriver' + profile = Selenium::WebDriver::Firefox::Profile.new + profile['general.useragent.override'] = "jamkazam" + + Capybara::Selenium::Driver.new(app, :profile => profile) + end + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 38af9186f..b0e1569e6 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -137,6 +137,41 @@ def set_cookie(k, v) end end +# def sign_in_poltergeist(user, options = {}) +# validate = options[:validate] +# validate = true if validate.nil? + +# if user.password.nil? && !options[:password] +# raise "user has no password. Use a user newly created so that it's password is still present" +# end +# uri = URI.parse(current_url) + +# # in tests, we often have an issue where an old signin screen is unloading +# # as this one is loading. +# # so one way to fix this is to go to a different page in this case, and then come back to /signin + +# if uri.path == signin_path +# visit '/' +# should_be_at_root +# end + +# visit signin_path +# page.should have_selector('#landing-inner form.signin-form') + +# within('#landing-inner form.signin-form') do +# fill_in "Email Address:", with: user.email +# fill_in "Password:", with: options[:password] || user.password +# click_button "SIGN IN" +# end + +# page.should have_no_selector('h1', text: 'sign in or register') + +# wait_until_curtain_gone + +# # presence of this means websocket gateway is not working +# page.should have_no_selector('.no-websocket-connection') if validate +#end + def sign_in_poltergeist(user, options = {}) validate = options[:validate] validate = true if validate.nil? @@ -175,6 +210,7 @@ end # skip the typical login form, which redirects to /client (slow due to extra login step). # So this just sets the cookie, and puts you where you want to be def fast_signin(user, url) + visit '/' set_login_cookie(user) visit url end @@ -486,7 +522,11 @@ def request_to_join_session(joiner, options) end def emulate_client - page.driver.headers = { 'User-Agent' => ' JamKazam ' } + #page.driver.headers = { 'User-Agent' => ' JamKazam ' } + #page.driver.header 'User-Agent', 'JamKazam' + #page.driver.options[:headers].merge!({ 'User-Agent' => ' JamKazam ' }) + #Capybara.current_session.driver.header('User-Agent', 'JamKazam') + # page.driver.browser.header('User-Agent', ' JamKazam ') end def create_join_session(creator, joiners=[], options={})