diff --git a/admin/app/admin/jam_track_right.rb b/admin/app/admin/jam_track_right.rb
index 5e7c3687b..3c591234a 100644
--- a/admin/app/admin/jam_track_right.rb
+++ b/admin/app/admin/jam_track_right.rb
@@ -64,7 +64,7 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
begin
client.find_or_create_account(user, billing_info)
- client.place_order(user, jam_track)
+ client.place_order(user, jam_track, nil)
rescue RecurlyClientError=>x
redirect_to admin_jam_track_rights_path, notice: "Could not order #{jam_track} for #{user.to_s}: #{x.errors.inspect}"
else
diff --git a/admin/spec/factories.rb b/admin/spec/factories.rb
index ba112d2e1..2dabcecc1 100644
--- a/admin/spec/factories.rb
+++ b/admin/spec/factories.rb
@@ -11,7 +11,7 @@ FactoryGirl.define do
state "NC"
country "US"
terms_of_service true
- resue_card true
+ reuse_card true
factory :admin do
diff --git a/db/manifest b/db/manifest
index 157316e1c..68e4fb17d 100755
--- a/db/manifest
+++ b/db/manifest
@@ -262,4 +262,4 @@ jam_track_importer.sql
jam_track_pro_licensing_update.sql
jam_track_redeemed.sql
shopping_cart_anonymous.sql
-user_reuse_card.sql
\ No newline at end of file
+user_reuse_card_and_reedem.sql
\ No newline at end of file
diff --git a/db/up/user_reuse_card.sql b/db/up/user_reuse_card.sql
deleted file mode 100644
index 27ac34d5b..000000000
--- a/db/up/user_reuse_card.sql
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TABLE users ADD COLUMN reuse_card BOOLEAN DEFAULT TRUE NOT NULL;
\ No newline at end of file
diff --git a/db/up/user_reuse_card_and_reedem.sql b/db/up/user_reuse_card_and_reedem.sql
new file mode 100644
index 000000000..2f2b811ea
--- /dev/null
+++ b/db/up/user_reuse_card_and_reedem.sql
@@ -0,0 +1,3 @@
+ALTER TABLE users ADD COLUMN reuse_card BOOLEAN DEFAULT TRUE NOT NULL;
+ALTER TABLE users ADD COLUMN has_redeemable_jamtrack BOOLEAN DEFAULT TRUE NOT NULL;
+ALTER TABLE shopping_carts ADD COLUMN marked_for_redeem INTEGER DEFAULT 0 NOT NULL;
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/models/anonymous_user.rb b/ruby/lib/jam_ruby/models/anonymous_user.rb
index e8964f349..048a3d85b 100644
--- a/ruby/lib/jam_ruby/models/anonymous_user.rb
+++ b/ruby/lib/jam_ruby/models/anonymous_user.rb
@@ -21,5 +21,9 @@ module JamRuby
def admin
false
end
+
+ def has_redeemable_jamtrack
+ false
+ end
end
end
diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb
index f441a441f..be833326c 100644
--- a/ruby/lib/jam_ruby/models/mix.rb
+++ b/ruby/lib/jam_ruby/models/mix.rb
@@ -141,12 +141,14 @@ module JamRuby
recording.recorded_tracks.each do |recorded_track|
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
- mix_params << { "level" => 1.0, "balance" => 0 }
+ mix_params << { "level" => 100, "balance" => 0 }
+ # change to 1.0 when ready to deploy new audiomixer
end
recording.recorded_backing_tracks.each do |recorded_backing_track|
manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
- mix_params << { "level" => 1.0, "balance" => 0 }
+ mix_params << { "level" => 100, "balance" => 0 }
+ # change to 1.0 when ready to deploy new audiomixer
end
recording.recorded_jam_track_tracks.each do |recorded_jam_track_track|
diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb
index 401278d84..24f4db02c 100644
--- a/ruby/lib/jam_ruby/models/shopping_cart.rb
+++ b/ruby/lib/jam_ruby/models/shopping_cart.rb
@@ -8,19 +8,32 @@ module JamRuby
validates :cart_id, presence: true
validates :cart_type, presence: true
validates :cart_class_name, presence: true
+ validates :marked_for_redeem, :inclusion => {:in => [true, false]}
default_scope order('created_at DESC')
def product_info
product = self.cart_product
- {name: product.name, price: product.price, product_id: cart_id} unless product.nil?
+ {name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, total_price: total_price(product), quantity: quantity, marked_for_redeem:marked_for_redeem} unless product.nil?
end
+ # multiply (quantity - redeemable) by price
+ def total_price(product)
+ (quantity - marked_for_redeem) * product.price
+ end
def cart_product
self.cart_class_name.classify.constantize.find_by_id self.cart_id unless self.cart_class_name.blank?
end
- def self.create user, product, quantity = 1
+ def redeem(mark_redeem)
+ self.marked_for_redeem = mark_redeem ? 1 : 0
+ end
+
+ def free?
+ marked_for_redeem == quantity
+ end
+
+ def self.create user, product, quantity = 1, mark_redeem = false
cart = ShoppingCart.new
if user.is_a?(User)
cart.user = user
@@ -32,8 +45,27 @@ module JamRuby
cart.cart_class_name = product.class.name
cart.cart_id = product.id
cart.quantity = quantity
+ cart.redeem(mark_redeem)
cart.save
cart
end
+
+ # if the user has a redeemable jam_track still on their account, then also check if any shopping carts have already been marked.
+ # if no shpping carts have been marked, then mark it redeemable
+ # should be wrapped in a TRANSACTION
+ def self.user_has_redeemable_jam_track?(any_user)
+ mark_redeem = false
+ if APP_CONFIG.one_free_jamtrack_per_user && any_user.has_redeemable_jamtrack
+ mark_redeem = true # start out assuming we can redeem...
+ shopping_carts.each do |shopping_cart|
+ # but if we find any shopping cart item already marked for redeem, then back out of mark_redeem=true
+ if shopping_cart.cart_type == product.class::PRODUCT_TYPE && shopping_cart.marked_for_redeem > 0
+ mark_redeem = false
+ break
+ end
+ end
+ end
+ mark_redeem
+ end
end
end
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 447438190..8b8b3deed 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -172,6 +172,7 @@ module JamRuby
validates :terms_of_service, :acceptance => {:accept => true, :on => :create, :allow_nil => false }
validates :reuse_card, :inclusion => {:in => [true, false]}
+ validates :has_redeemable_jamtrack, :inclusion => {:in => [true, false]}
validates :subscribe_email, :inclusion => {:in => [nil, true, false]}
validates :musician, :inclusion => {:in => [true, false]}
validates :show_whats_next, :inclusion => {:in => [nil, true, false]}
diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb
index 96c4df57e..955f702fe 100644
--- a/ruby/lib/jam_ruby/recurly_client.rb
+++ b/ruby/lib/jam_ruby/recurly_client.rb
@@ -4,7 +4,7 @@ module JamRuby
def initialize()
end
- def create_account(current_user, billing_info=nil)
+ def create_account(current_user, billing_info)
options = account_hash(current_user, billing_info)
account = nil
begin
@@ -37,7 +37,7 @@ module JamRuby
end
def get_account(current_user)
- (current_user && current_user.recurly_code) ? Recurly::Account.find(current_user.recurly_code) : nil
+ current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
end
def update_account(current_user, billing_info=nil)
@@ -53,12 +53,12 @@ module JamRuby
account
end
- def update_billing_info(current_user, billing_info=nil)
+ def update_billing_info(current_user, billing_info)
account = get_account(current_user)
if (account.present?)
begin
- account.billing_info=billing_info
- account.billing_info.save
+ account.billing_info = billing_info
+ account.billing_info.save
rescue Recurly::Error, NoMethodError => x
raise RecurlyClientError, x.to_s
end
@@ -145,7 +145,7 @@ module JamRuby
raise RecurlyClientError.new(plan.errors) if plan.errors.any?
end
- def place_order(current_user, jam_track)
+ def place_order(current_user, jam_track, shopping_cart)
jam_track_right = nil
account = get_account(current_user)
if (account.present?)
@@ -163,10 +163,17 @@ module JamRuby
# this means we already have a subscription, so don't try to create a new one for the same plan (Recurly would fail this anyway)
unless recurly_subscription_uuid
- subscription = Recurly::Subscription.create(:account=>account, :plan_code=>jam_track.plan_code)
+ # if the shopping cart was specified, see if the item should be free
+ free = shopping_cart.nil? ? false : shopping_cart.free?
+ # and if it's free, squish the charge to 0.
+ unit_amount_in_cents = free ? 0 : nil
+ subscription = Recurly::Subscription.create(:account=>account, :plan_code=>jam_track.plan_code, unit_amount_in_cents: unit_amount_in_cents)
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
+ # delete from shopping cart the subscription
+ shopping_cart.destroy if shopping_cart
+
# Reload and make sure it went through:
account = get_account(current_user)
@@ -180,7 +187,7 @@ module JamRuby
raise RecurlyClientError, "Plan code '#{paid_subscription.plan_code}' doesn't match jam track: '#{jam_track.plan_code}'" unless recurly_subscription_uuid
- jam_track_right=JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id)
+ jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id)
if jam_track_right.recurly_subscription_uuid != recurly_subscription_uuid
jam_track_right.recurly_subscription_uuid = recurly_subscription_uuid
jam_track_right.save
@@ -198,7 +205,7 @@ module JamRuby
jam_track_right
end
- def find_or_create_account(current_user, billing_info=nil)
+ def find_or_create_account(current_user, billing_info)
account = get_account(current_user)
if(account.nil?)
diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb
index c81f6d38e..418399a8f 100644
--- a/ruby/spec/factories.rb
+++ b/ruby/spec/factories.rb
@@ -18,7 +18,7 @@ FactoryGirl.define do
musician true
terms_of_service true
last_jam_audio_latency 5
- resue_card true
+ reuse_card true
#u.association :musician_instrument, factory: :musician_instrument, user: u
diff --git a/ruby/spec/jam_ruby/recurly_client_spec.rb b/ruby/spec/jam_ruby/recurly_client_spec.rb
index 0245c91e9..078ad8a5c 100644
--- a/ruby/spec/jam_ruby/recurly_client_spec.rb
+++ b/ruby/spec/jam_ruby/recurly_client_spec.rb
@@ -89,7 +89,7 @@ describe RecurlyClient do
it "can place order" do
@client.find_or_create_account(@user, @billing_info)
- expect{@client.place_order(@user, @jamtrack)}.not_to raise_error()
+ expect{@client.place_order(@user, @jamtrack, nil)}.not_to raise_error()
subs = @client.get_account(@user).subscriptions
subs.should_not be_nil
subs.should have(1).items
@@ -102,7 +102,7 @@ describe RecurlyClient do
@client.find_or_create_account(@user, @billing_info)
# Place order:
- expect{@client.place_order(@user, @jamtrack)}.not_to raise_error()
+ expect{@client.place_order(@user, @jamtrack, nil)}.not_to raise_error()
active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'}
@jamtrack.reload
@jamtrack.jam_track_rights.should have(1).items
@@ -118,10 +118,10 @@ describe RecurlyClient do
it "detects error on double order" do
@client.find_or_create_account(@user, @billing_info)
- jam_track_right = @client.place_order(@user, @jamtrack)
+ jam_track_right = @client.place_order(@user, @jamtrack, nil)
jam_track_right.recurly_subscription_uuid.should_not be_nil
- jam_track_right2 = @client.place_order(@user, @jamtrack)
+ jam_track_right2 = @client.place_order(@user, @jamtrack, nil)
jam_track_right.should eq(jam_track_right2)
jam_track_right.recurly_subscription_uuid.should eq(jam_track_right.recurly_subscription_uuid)
end
diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb
index cab08fab2..94294b86c 100644
--- a/ruby/spec/support/utilities.rb
+++ b/ruby/spec/support/utilities.rb
@@ -166,6 +166,10 @@ def app_config
20 # 20 seconds
end
+ def one_free_jamtrack_per_user
+ true
+ end
+
private
diff --git a/web/app/assets/javascripts/checkout_order.js b/web/app/assets/javascripts/checkout_order.js
new file mode 100644
index 000000000..53bcc7330
--- /dev/null
+++ b/web/app/assets/javascripts/checkout_order.js
@@ -0,0 +1,368 @@
+(function (context, $) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.CheckoutOrderScreen = function (app) {
+
+ var EVENTS = context.JK.EVENTS;
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var jamTrackUtils = context.JK.JamTrackUtils;
+
+ var $screen = null;
+ var $navigation = null;
+ var $templateOrderContent = null;
+ var $templatePurchasedJamTrack = null;
+ var $orderPanel = null;
+ var $thanksPanel = null;
+ var $jamTrackInBrowser = null;
+ var $purchasedJamTrack = null;
+ var $purchasedJamTrackHeader = null;
+ var $purchasedJamTracks = null;
+ var $orderContent = null;
+ var userDetail = null;
+ var step = null;
+ var downloadJamTracks = [];
+ var purchasedJamTracks = null;
+ var purchasedJamTrackIterator = 0;
+ var $backBtn = null;
+ var $paymentPrompt = null;
+ var $emptyCartPrompt = null;
+
+
+ function beforeShow() {
+ beforeShowOrder();
+ }
+
+
+ function afterShow(data) {
+
+ }
+
+
+ function beforeHide() {
+ if(downloadJamTracks) {
+ context._.each(downloadJamTracks, function(downloadJamTrack) {
+ downloadJamTrack.destroy();
+ downloadJamTrack.root.remove();
+ })
+
+ downloadJamTracks = [];
+ }
+ purchasedJamTracks = null;
+ purchasedJamTrackIterator = 0;
+ }
+
+ function beforeShowOrder() {
+ $paymentPrompt.addClass('hidden')
+ $emptyCartPrompt.addClass('hidden')
+ $orderPanel.removeClass("hidden")
+ $thanksPanel.addClass("hidden")
+ $orderContent.find(".place-order").addClass('disabled').off('click', placeOrder)
+ step = 3;
+ renderNavigation();
+ populateOrderPage();
+ }
+
+
+ function populateOrderPage() {
+ clearOrderPage();
+
+ rest.getShoppingCarts()
+ .done(function(carts) {
+ rest.getBillingInfo()
+ .done(function(billingInfo) {
+ renderOrderPage(carts, billingInfo)
+ })
+ .fail(function(jqXHR) {
+ if(jqXHR.status == 404) {
+ // no account for this user
+ app.notify({ title: "No account information",
+ text: "Please restart the checkout process." },
+ null,
+ true);
+ }
+ })
+ })
+ .fail(app.ajaxError);
+ }
+
+
+ function renderOrderPage(carts, recurlyAccountInfo) {
+ logger.debug("rendering order page")
+ var data = {}
+
+ var sub_total = 0.0
+ var taxes = 0.0
+ $.each(carts, function(index, cart) {
+ sub_total += parseFloat(cart.product_info.total_price)
+ });
+ //data.grand_total = (sub_total + taxes).toFixed(2)
+ data.sub_total = sub_total.toFixed(2)
+ //data.taxes = taxes.toFixed(2)
+ data.carts = carts
+ data.billing_info = recurlyAccountInfo.billing_info
+ data.shipping_info = recurlyAccountInfo.address
+
+ data.shipping_as_billing = true; //jamTrackUtils.compareAddress(data.billing_info, data.shipping_info);
+
+ var orderContentHtml = $(
+ context._.template(
+ $templateOrderContent.html(),
+ data,
+ {variable: 'data'}
+ )
+ )
+
+ $orderContent.append(orderContentHtml)
+ $orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo)
+
+ var $placeOrder = $screen.find(".place-order")
+ if(carts.length == 0) {
+ $paymentPrompt.addClass('hidden')
+ $emptyCartPrompt.removeClass('hidden')
+ $placeOrder.addClass('disabled')
+ }
+ else {
+ logger.debug("cart has " + carts.length + " items in it")
+ $paymentPrompt.removeClass('hidden')
+ $emptyCartPrompt.addClass('hidden')
+ $placeOrder.removeClass('disabled').on('click', placeOrder)
+
+ var planPricing = {}
+
+ context._.each(carts, function(cart) {
+ var priceElement = $screen.find('.order-right-page .plan[data-plan-code="' + cart.product_info.plan_code +'"]')
+
+ if(priceElement.length == 0) {
+ logger.error("unable to find price element for " + cart.product_info.plan_code, cart);
+ app.notify({title: "Error Encountered", text: "Unable to find plan info for " + cart.product_info.plan_code})
+ return false;
+ }
+
+ logger.debug("creating recurly pricing element for plan: " + cart.product_info.plan_code)
+ var pricing = context.recurly.Pricing();
+ pricing.plan_code = cart.product_info.plan_code;
+ pricing.resolved = false;
+ pricing.effective_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
+ planPricing[pricing.plan_code] = pricing;
+
+ // this is called when the plan is resolved against Recurly. It will have tax info, which is the only way we can get it.
+ pricing.on('change', function(price) {
+
+ var resolvedPrice = planPricing[this.plan_code];
+ if(!resolvedPrice) {
+ logger.error("unable to find price info in storage")
+ app.notify({title: "Error Encountered", text: "Unable to find plan info in storage"})
+ return;
+ }
+ else {
+ logger.debug("pricing resolved for plan: " + this.plan_code)
+ }
+ resolvedPrice.resolved = true;
+
+ var allResolved = true;
+ var totalTax = 0;
+ var totalPrice = 0;
+
+ // let's see if all plans have been resolved via API; and add up total price and taxes for display
+ $.each(planPricing, function(plan_code, priceObject) {
+ logger.debug("resolved recurly priceObject", priceObject)
+
+ if(!priceObject.resolved) {
+ allResolved = false;
+ return false;
+ }
+ else {
+ var unitTax = Number(priceObject.price.now.tax) * priceObject.effective_quantity;
+ totalTax += unitTax;
+
+ var totalUnitPrice = Number(priceObject.price.now.total) * priceObject.effective_quantity;
+ totalPrice += totalUnitPrice;
+ }
+ })
+
+ if(allResolved) {
+ $screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2))
+ $screen.find('.order-right-page .order-items-value.order-total').text('$' + totalPrice.toFixed(2))
+ }
+ else
+ {
+ logger.debug("still waiting on more plans to resolve")
+ }
+ })
+ pricing.attach(priceElement.eq(0))
+ })
+ }
+ }
+
+ function moveToPaymentInfo() {
+ context.location = '/client#/checkoutPayment';
+ return false;
+ }
+
+ function placeOrder(e) {
+ e.preventDefault();
+ $screen.find(".place-order").off('click').addClass('disabled')
+ rest.placeOrder()
+ .done(moveToThanks)
+ .fail(orderErrorHandling);
+ }
+
+
+ function orderErrorHandling(xhr, ajaxOptions, thrownError) {
+ if (xhr && xhr.responseJSON) {
+ var message = "Error submitting payment: "
+ $.each(xhr.responseJSON.errors, function (key, error) {
+ message += key + ": " + error
+ })
+ $("#order_error").text(message).removeClass("hidden")
+ }
+ else {
+ $("#order_error").text(xhr.responseText).removeClass("hidden")
+ }
+ $orderContent.find(".place-order").on('click', placeOrder)
+ }
+
+ function moveToThanks(purchaseResponse) {
+ $("#order_error").addClass("hidden")
+ $orderPanel.addClass("hidden")
+ $thanksPanel.removeClass("hidden")
+ jamTrackUtils.checkShoppingCart()
+ //beforeShowOrder()
+ handleJamTracksPurchased(purchaseResponse.jam_tracks)
+ }
+
+ function handleJamTracksPurchased(jamTracks) {
+ // were any JamTracks purchased?
+ var jamTracksPurchased = jamTracks && jamTracks.length > 0;
+ if(jamTracksPurchased) {
+ if(gon.isNativeClient) {
+ startDownloadJamTracks(jamTracks)
+ }
+ else {
+ $jamTrackInBrowser.removeClass('hidden');
+ }
+ }
+ }
+
+ function startDownloadJamTracks(jamTracks) {
+ // there can be multiple purchased JamTracks, so we cycle through them
+
+ purchasedJamTracks = jamTracks;
+
+ // populate list of jamtracks purchased, that we will iterate through graphically
+ context._.each(jamTracks, function(jamTrack) {
+ var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small');
+ var $purchasedJamTrack = $(context._.template(
+ $templatePurchasedJamTrack.html(),
+ jamTrack,
+ {variable: 'data'}
+ ));
+
+ $purchasedJamTracks.append($purchasedJamTrack)
+
+ // show it on the page
+ $purchasedJamTrack.append(downloadJamTrack.root)
+
+ downloadJamTracks.push(downloadJamTrack)
+ })
+
+ iteratePurchasedJamTracks();
+ }
+
+ function iteratePurchasedJamTracks() {
+ if(purchasedJamTrackIterator < purchasedJamTracks.length ) {
+ var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++];
+
+ // make sure the 'purchasing JamTrack' section can be seen
+ $purchasedJamTrack.removeClass('hidden');
+
+ // the widget indicates when it gets to any transition; we can hide it once it reaches completion
+ $(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) {
+
+ if(data.state == downloadJamTrack.states.synchronized) {
+ logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;")
+ //downloadJamTrack.root.remove();
+ downloadJamTrack.destroy();
+
+ // go to the next JamTrack
+ iteratePurchasedJamTracks()
+ }
+ })
+
+ logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing")
+
+ // kick off the download JamTrack process
+ downloadJamTrack.init()
+
+ // XXX style-test code
+ // downloadJamTrack.transitionError("package-error", "The server failed to create your package.")
+
+ }
+ else {
+ logger.debug("done iterating over purchased JamTracks")
+ $purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.')
+ }
+ }
+
+ function clearOrderPage() {
+ $orderContent.empty();
+ }
+
+ function renderNavigation() {
+ $navigation.html("");
+ var navigationHtml = $(
+ context._.template(
+ $('#template-checkout-navigation').html(),
+ {current: step},
+ {variable: 'data'}
+ )
+ );
+
+ $navigation.append(navigationHtml);
+ }
+
+ function events() {
+ $backBtn.on('click', function(e) {
+ e.preventDefault();
+
+ context.location = '/client#/checkoutPayment'
+ })
+ }
+
+ function initialize() {
+ var screenBindings = {
+ 'beforeShow': beforeShow,
+ 'afterShow': afterShow,
+ 'beforeHide': beforeHide
+ };
+ app.bindScreen('checkoutOrder', screenBindings);
+
+ $screen = $("#checkoutOrderScreen");
+ $navigation = $screen.find(".checkout-navigation-bar");
+ $templateOrderContent = $("#template-order-content");
+ $templatePurchasedJamTrack = $('#template-purchased-jam-track');
+ $orderPanel = $screen.find(".order-panel");
+ $thanksPanel = $screen.find(".thanks-panel");
+ $jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser");
+ $purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
+ $purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
+ $purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")
+ $backBtn = $screen.find('.back');
+ $paymentPrompt = $screen.find('.payment-prompt');
+ $emptyCartPrompt = $screen.find('.empty-cart-prompt');
+ $orderContent = $orderPanel.find(".order-content");
+
+ if ($screen.length == 0) throw "$screen must be specified";
+ if ($navigation.length == 0) throw "$navigation must be specified";
+
+ events();
+ }
+
+ this.initialize = initialize;
+
+ return this;
+ }
+})
+(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/checkout_payment.js b/web/app/assets/javascripts/checkout_payment.js
index aefe06be6..c492ddf3e 100644
--- a/web/app/assets/javascripts/checkout_payment.js
+++ b/web/app/assets/javascripts/checkout_payment.js
@@ -6,6 +6,7 @@
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
+ var jamTrackUtils = context.JK.JamTrackUtils;
var $screen = null;
var $navigation = null;
@@ -21,8 +22,17 @@
var billing_info = null;
var shipping_info = null;
var shipping_as_billing = null;
+ var $reuseExistingCard = null;
+ var $reuseExistingCardChk = null;
+ var $existingCardEndsWith = null;
+ var $newCardInfo = null;
+ var selectCountry = null;
+ var selectCountryLoaded = false;
+ var $freeJamTrackPrompt = null;
+ var $noFreeJamTrackPrompt = null;
+
+ function afterShow() {
- function beforeShow() {
beforeShowPaymentInfo();
}
@@ -30,39 +40,76 @@
step = 2;
renderNavigation();
renderAccountInfo();
+
}
function renderAccountInfo() {
- var user = rest.getUserDetail()
+ $reuseExistingCard.addClass('hidden');
+ $newCardInfo.removeClass('hidden');
+ $freeJamTrackPrompt.addClass('hidden');
+ $noFreeJamTrackPrompt.addClass('hidden');
+
+ var selectCountryReady = selectCountry.ready();
+ if(!selectCountryReady) {
+ // one time init of country dropdown
+ selectCountryReady = selectCountry.load('US', null, null);
+ }
+
+ selectCountryReady.done(function() {
+ var user = rest.getUserDetail()
if(user) {
user.done(populateAccountInfo).error(app.ajaxError);
}
+ })
}
function populateAccountInfo(user) {
userDetail = user;
+ $reuseExistingCardChk.iCheck(userDetail.reuse_card ? 'check' : 'uncheck').attr('checked', userDetail.reuse_card)
+
+ // show appropriate prompt text based on whether user has a free jamtrack
+ if(user.free_jamtrack) {
+ $freeJamTrackPrompt.removeClass('hidden')
+ }
+ else {
+ $noFreeJamTrackPrompt.removeClass('hidden')
+ }
+
if (userDetail.has_recurly_account) {
+
rest.getBillingInfo()
.done(function(response) {
- $billingInfo.find("#billing-first-name").val(response.first_name);
- $billingInfo.find("#billing-last-name").val(response.last_name);
- $billingInfo.find("#billing-address1").val(response.address1);
- $billingInfo.find("#billing-address2").val(response.address2);
- $billingInfo.find("#billing-city").val(response.city);
- $billingInfo.find("#billing-state").val(response.state);
- $billingInfo.find("#billing-zip").val(response.zip);
- $billingInfo.find("#billing-country").val(response.country);
- $shippingAddress.find("#shipping-first-name").val(response.first_name);
- $shippingAddress.find("#shipping-last-name").val(response.last_name);
- $shippingAddress.find("#shipping-address1").val(response.address1);
- $shippingAddress.find("#shipping-address2").val(response.address2);
- $shippingAddress.find("#shipping-city").val(response.city);
- $shippingAddress.find("#shipping-state").val(response.state);
- $shippingAddress.find("#shipping-zip").val(response.zip);
- $shippingAddress.find("#shipping-country").val(response.country);
+ if(userDetail.reuse_card) {
+ $reuseExistingCard.removeClass('hidden');
+ toggleReuseExistingCard.call($reuseExistingCardChk)
+ $existingCardEndsWith.text(response.billing_info.last_four);
+ }
+
+ var isSameAsShipping = true // jamTrackUtils.compareAddress(response.billing_info, response.address);
+
+ $shippingAsBilling.iCheck(isSameAsShipping ? 'check' : 'uncheck').attr('checked', isSameAsShipping)
+
+ $billingInfo.find("#billing-first-name").val(response.billing_info.first_name);
+ $billingInfo.find("#billing-last-name").val(response.billing_info.last_name);
+ $billingInfo.find("#billing-address1").val(response.billing_info.address1);
+ $billingInfo.find("#billing-address2").val(response.billing_info.address2);
+ $billingInfo.find("#billing-city").val(response.billing_info.city);
+ $billingInfo.find("#billing-state").val(response.billing_info.state);
+ $billingInfo.find("#billing-zip").val(response.billing_info.zip);
+ $billingInfo.find("#billing-country").val(response.billing_info.country);
+
+ //$shippingAddress.find("#shipping-first-name").val(response.billing_info.first_name);
+ //$shippingAddress.find("#shipping-last-name").val(response.billing_info.last_name);
+ //$shippingAddress.find("#shipping-address1").val(response.address.address1);
+ //$shippingAddress.find("#shipping-address2").val(response.address.address2);
+ //$shippingAddress.find("#shipping-city").val(response.address.city);
+ //$shippingAddress.find("#shipping-state").val(response.address.state);
+ //$shippingAddress.find("#shipping-zip").val(response.address.zip);
+ //$shippingAddress.find("#shipping-country").val(response.address.country);
+
})
.error(app.ajaxError);
}
@@ -81,7 +128,7 @@
}
}
- function afterShow(data) {
+ function beforeShow(data) {
// XXX : style-test code
// moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]});
}
@@ -96,8 +143,14 @@
// TODO: Refactor: this function is long and fraught with many return points.
function next(e) {
+ $paymentInfoPanel.find('.error-text').remove();
+ $paymentInfoPanel.find('.error').removeClass('error');
e.preventDefault();
- $("#order_error").addClass("hidden")
+ $("#payment_error").addClass("hidden")
+
+ var reuse_card_this_time = $reuseExistingCardChk.is(':checked');
+ var reuse_card_next_time = $paymentMethod.find('#save-card').is(':checked');
+
// validation
var billing_first_name = $billingInfo.find("#billing-first-name").val();
@@ -114,6 +167,7 @@
$billingInfo.find('#divBillingFirstName').addClass("error");
$billingInfo.find('#billing-first-name').after("
");
+ logger.info("no billing first name");
return false;
}
else {
@@ -125,6 +179,7 @@
$billingInfo.find('#divBillingLastName').addClass("error");
$billingInfo.find('#billing-last-name').after("");
+ logger.info("no billing last name");
return false;
}
else {
@@ -136,6 +191,7 @@
$billingInfo.find('#divBillingAddress1').addClass("error");
$billingInfo.find('#billing-address1').after("");
+ logger.info("no billing address line 1");
return false;
}
else {
@@ -147,6 +203,7 @@
$billingInfo.find('#divBillingZip').addClass("error");
$billingInfo.find('#billing-zip').after("");
+ logger.info("no billing address line 2");
return false;
}
else {
@@ -158,6 +215,7 @@
$billingInfo.find('#divBillingState').addClass("error");
$billingInfo.find('#billing-zip').after("");
+ logger.info("no billing zip");
return false;
}
else {
@@ -169,6 +227,7 @@
$billingInfo.find('#divBillingCity').addClass("error");
$billingInfo.find('#billing-city').after("");
+ logger.info("no billing city");
return false;
}
else {
@@ -180,12 +239,14 @@
$billingInfo.find('#divBillingCountry').addClass("error");
$billingInfo.find('#billing-country').after("");
+ logger.info("no billing country");
return false;
}
else {
$billingInfo.find('#divBillingCountry').removeClass("error");
}
+ /**
shipping_as_billing = $shippingAsBilling.is(":checked");
var shipping_first_name, shipping_last_name, shipping_address1, shipping_address2;
var shipping_city, shipping_state, shipping_zip, shipping_country;
@@ -205,6 +266,7 @@
$shippingAddress.find('#divShippingFirstName').addClass("error");
$shippingAddress.find('#shipping-first-name').after("");
+ logger.info("no address first name");
return false;
}
else {
@@ -216,6 +278,7 @@
$shippingAddress.find('#divShippingLastName').addClass("error");
$shippingAddress.find('#shipping-last-name').after("");
+ logger.info("no last name");
return false;
}
else {
@@ -227,6 +290,7 @@
$shippingAddress.find('#divShippingAddress1').addClass("error");
$shippingAddress.find('#shipping-address1').after("");
+ logger.info("no shipping address 1");
return false;
}
else {
@@ -238,6 +302,7 @@
$shippingAddress.find('#divShippingZip').addClass("error");
$shippingAddress.find('#shipping-zip').after("");
+ logger.info("no shipping address 2");
return false;
}
else {
@@ -249,6 +314,7 @@
$shippingAddress.find('#divShippingState').addClass("error");
$shippingAddress.find('#shipping-zip').after("");
+ logger.info("no shipping state");
return false;
}
else {
@@ -260,6 +326,7 @@
$shippingAddress.find('#divShippingCity').addClass("error");
$shippingAddress.find('#shipping-city').after("");
+ logger.info("no shipping city");
return false;
}
else {
@@ -271,12 +338,14 @@
$shippingAddress.find('#divShippingCountry').addClass("error");
$shippingAddress.find('#shipping-country').after("");
+ logger.info("no shipping country");
return false;
}
else {
$shippingAddress.find('#divShippingCountry').removeClass("error");
}
}
+ */
var card_name = $paymentMethod.find("#card-name").val();
var card_number = $paymentMethod.find("#card-number").val();
@@ -284,6 +353,7 @@
var card_month = $paymentMethod.find("#card_expire-date_2i").val();
var card_verify = $paymentMethod.find("#card-verify").val();
+ /**
if (!card_name) {
$paymentMethod.find('#divCardName .error-text').remove();
$paymentMethod.find('#divCardName').addClass("error");
@@ -291,46 +361,54 @@
return false;
} else {
$paymentMethod.find('#divCardName').removeClass("error");
+ }*/
+
+ // don't valid card form fields when reuse card selected
+ if(!reuse_card_this_time) {
+ if (!card_number) {
+ $paymentMethod.find('#divCardNumber .error-text').remove();
+ $paymentMethod.find('#divCardNumber').addClass("error");
+ $paymentMethod.find('#card-number').after("");
+ logger.info("no card number");
+ return false;
+ } else if (!$.payment.validateCardNumber(card_number)) {
+ $paymentMethod.find('#divCardNumber .error-text').remove();
+ $paymentMethod.find('#divCardNumber').addClass("error");
+ $paymentMethod.find('#card-number').after("");
+ logger.info("invalid card number");
+ return false;
+ } else {
+ $paymentMethod.find('#divCardNumber').removeClass("error");
+ }
+
+ if (!$.payment.validateCardExpiry(card_month, card_year)) {
+ $paymentMethod.find('#divCardExpiry .error-text').remove();
+ $paymentMethod.find('#divCardExpiry').addClass("error");
+ $paymentMethod.find('#card-expiry').after("");
+ logger.info("invalid card expiry");
+ return false;
+ } else {
+ $paymentMethod.find('#divCardExpiry').removeClass("error");
+ }
+
+ if (!card_verify) {
+ $paymentMethod.find('#divCardVerify .error-text').remove();
+ $paymentMethod.find('#divCardVerify').addClass("error");
+ $paymentMethod.find('#card-verify').after("- Card Verification Value is required
");
+
+ logger.info("no card verify");
+ return false;
+ } else if (!$.payment.validateCardCVC(card_verify)) {
+ $paymentMethod.find('#divCardVerify .error-text').remove();
+ $paymentMethod.find('#divCardVerify').addClass("error");
+ $paymentMethod.find('#card-verify').after("- Card Verification Value is not valid.
");
+
+ logger.info("bad card CVC");
+ return false;
+ } else {
+ $paymentMethod.find('#divCardVerify').removeClass("error");
+ }
}
-
- if (!card_number) {
- $paymentMethod.find('#divCardNumber .error-text').remove();
- $paymentMethod.find('#divCardNumber').addClass("error");
- $paymentMethod.find('#card-number').after("");
- return false;
- } else if (!$.payment.validateCardNumber(card_number)) {
- $paymentMethod.find('#divCardNumber .error-text').remove();
- $paymentMethod.find('#divCardNumber').addClass("error");
- $paymentMethod.find('#card-number').after("");
- return false;
- } else {
- $paymentMethod.find('#divCardNumber').removeClass("error");
- }
-
- if (!$.payment.validateCardExpiry(card_month, card_year)) {
- $paymentMethod.find('#divCardExpiry .error-text').remove();
- $paymentMethod.find('#divCardExpiry').addClass("error");
- $paymentMethod.find('#card-expiry').after("");
- } else {
- $paymentMethod.find('#divCardExpiry').removeClass("error");
- }
-
- if (!card_verify) {
- $paymentMethod.find('#divCardVerify .error-text').remove();
- $paymentMethod.find('#divCardVerify').addClass("error");
- $paymentMethod.find('#card-verify').after("- Card Verification Value is required
");
-
- return false;
- } else if(!$.payment.validateCardCVC(card_verify)) {
- $paymentMethod.find('#divCardVerify .error-text').remove();
- $paymentMethod.find('#divCardVerify').addClass("error");
- $paymentMethod.find('#card-verify').after("- Card Verification Value is not valid.
");
-
- return false;
- } else {
- $paymentMethod.find('#divCardVerify').removeClass("error");
- }
-
billing_info = {};
shipping_info = {};
billing_info.first_name = billing_first_name;
@@ -346,6 +424,7 @@
billing_info.year = card_year;
billing_info.verification_value = card_verify;
+ /**
if (shipping_as_billing) {
shipping_info = $.extend({},billing_info);
delete shipping_info.number;
@@ -361,69 +440,83 @@
shipping_info.state = shipping_state;
shipping_info.country = shipping_country;
shipping_info.zip = shipping_zip;
- }
-
- $paymentInfoPanel.find("#payment-info-next").addClass("disabled");
- $paymentInfoPanel.find("#payment-info-next").off("click");
-
- var reuse_card = $paymentMethod.find('#save-card').is(':checked');
-
+ }*/
var email = null;
var password = null;
+ var terms = false;
var isLoggedIn = context.JK.currentUserId;
if(!isLoggedIn) {
email = $accountSignup.find('input[name="email"]').val()
password = $accountSignup.find('input[name="password"]').val()
+ terms = $accountSignup.find('input[name="terms-of-service"]').is(':checked');
}
- rest.createRecurlyAccount({billing_info: billing_info, terms_of_service: true, email: email, password: password, reuse_card: reuse_card})
+ $screen.find("#payment-info-next").addClass("disabled");
+ $screen.find("#payment-info-next").off("click");
+ rest.createRecurlyAccount({billing_info: billing_info, terms_of_service: terms, email: email, password: password, reuse_card_this_time: reuse_card_this_time, reuse_card_next_time: reuse_card_next_time})
.done(function() {
- $paymentInfoPanel.find("#payment-info-next").removeClass("disabled");
- $paymentInfoPanel.find("#payment-info-next").on("click", next);
+ $screen.find("#payment-info-next").on("click", next);
if(isLoggedIn) {
- context.location = '/client#checkout_order'
+ context.location = '/client#/checkoutOrder'
}
else {
// this means the account was created; we need to reload the page for this to take effect
- context.location = '/client#checkout_order'
+ context.JK.currentUserId = 'something' // this is to trick layout.js from getting involved and redirecting to home screen
+ context.location = '/client#/checkoutOrder'
context.location.reload()
}
})
- .fail(errorHandling);
+ .fail(errorHandling)
+ .always(function(){
+ $screen.find("#payment-info-next").removeClass("disabled");
+ })
}
function errorHandling(xhr, ajaxOptions, thrownError) {
- $.each(xhr.responseJSON.errors, function(key, error) {
- if (key == 'number') {
- $paymentMethod.find('#divCardNumber .error-text').remove();
- $paymentMethod.find('#divCardNumber').addClass("error");
- $paymentMethod.find('#card-number').after("");
- }
- else if (key == 'verification_value') {
- $paymentMethod.find('#divCardVerify .error-text').remove();
- $paymentMethod.find('#divCardVerify').addClass("error");
- $paymentMethod.find('#card-verify').after("");
- }
- else if(key == 'email') {
- var $email = $accountSignup.find('input[name="email"]')
- var $field = $email.closest('.field')
- $field.find('.error-text').remove()
- $field.addClass("error");
- $field.append("");
- }
- else if(key == 'password') {
- var $email = $accountSignup.find('input[name="password"]')
- var $field = $email.closest('.field')
- $field.find('.error-text').remove()
- $field.addClass("error");
- $field.append("");
- }
- });
+ logger.debug("error handling", xhr.responseJSON)
+ if(xhr.responseJSON && xhr.responseJSON.errors) {
+ $.each(xhr.responseJSON.errors, function(key, error) {
+ if (key == 'number') {
+ $paymentMethod.find('#divCardNumber .error-text').remove();
+ $paymentMethod.find('#divCardNumber').addClass("error");
+ $paymentMethod.find('#card-number').after("");
+ }
+ else if (key == 'verification_value') {
+ $paymentMethod.find('#divCardVerify .error-text').remove();
+ $paymentMethod.find('#divCardVerify').addClass("error");
+ $paymentMethod.find('#card-verify').after("");
+ }
+ else if(key == 'email') {
+ var $email = $accountSignup.find('input[name="email"]')
+ var $field = $email.closest('.field')
+ $field.find('.error-text').remove()
+ $field.addClass("error");
+ $field.append("");
+ }
+ else if(key == 'password') {
+ var $password = $accountSignup.find('input[name="password"]')
+ var $field = $password.closest('.field')
+ $field.find('.error-text').remove()
+ $field.addClass("error");
+ $field.append("");
+ }
+ else if(key == 'terms_of_service') {
+ var $terms = $accountSignup.find('input[name="terms-of-service"]')
+ var $field = $terms.closest('.field')
+ $field.find('.error-text').remove()
+ $field.addClass("error");
+ $field.append("");
+ }
+ });
+ }
+ else {
+ $("#payment_error").text(xhr.responseText)
+ }
- $paymentInfoPanel.find("#payment-info-next").addClass("disabled");
- $paymentInfoPanel.find("#payment-info-next").on('click', next);
+
+ $screen.find("#payment-info-next").on('click', next);
}
function beforeShowOrder() {
@@ -440,10 +533,6 @@
.fail(app.ajaxError);
}
- function moveToOrder() {
- window.location = '/client#/checkout_order';
- }
-
function toggleShippingAsBilling(e) {
e.preventDefault();
@@ -457,9 +546,36 @@
}
}
+ function toggleReuseExistingCard(e) {
+ if(e) {
+ e.preventDefault();
+ }
+
+ logger.debug("toggle reuse existing card")
+
+ var reuse_existing = $(this).is(':checked');
+
+ $('#billing-first-name').prop('disabled', reuse_existing);
+ $('#billing-last-name').prop('disabled', reuse_existing);
+ $('#billing-address1').prop('disabled', reuse_existing);
+ $('#billing-address2').prop('disabled', reuse_existing);
+ $('#billing-city').prop('disabled', reuse_existing);
+ $('#billing-state').prop('disabled', reuse_existing);
+ $('#billing-zip').prop('disabled', reuse_existing);
+ $('#billing-country').prop('disabled', reuse_existing);
+
+ $('#card-name').prop('disabled', reuse_existing);
+ $('#card-number').prop('disabled', reuse_existing);
+ $('#card_expire-date_1i').prop('disabled', reuse_existing);
+ $('#card_expire-date_2i').prop('disabled', reuse_existing);
+ $('#card-verify').prop('disabled', reuse_existing);
+
+ }
+
function events() {
$screen.find("#payment-info-next").on('click', next);
$shippingAsBilling.on('ifChanged', toggleShippingAsBilling);
+ $reuseExistingCardChk.on('ifChanged', toggleReuseExistingCard);
}
function reset() {
@@ -489,6 +605,8 @@
// Use jquery.payment to limit characters and length:
$paymentMethod.find("#card-number").payment('formatCardNumber');
$paymentMethod.find("#card-verify").payment('formatCardCVC');
+
+ selectCountry = new context.JK.SelectLocation($('#billing-country'), null, null, app, false)
}
function initialize() {
@@ -497,7 +615,7 @@
'afterShow': afterShow,
'beforeHide' : beforeHide
};
- app.bindScreen('checkout_payment', screenBindings);
+ app.bindScreen('checkoutPayment', screenBindings);
$screen = $("#checkoutPaymentScreen");
$paymentInfoPanel = $screen.find("#checkout-payment-info");
@@ -508,6 +626,13 @@
$accountSignup = $paymentInfoPanel.find('.jamkazam-account-signup')
$shippingAddress = $paymentInfoPanel.find(".shipping-address-detail");
$shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing");
+ $reuseExistingCard = $paymentInfoPanel.find('.reuse-existing-card')
+ $reuseExistingCardChk = $paymentInfoPanel.find('#reuse-existing-card')
+ $existingCardEndsWith = $paymentInfoPanel.find('.existing-card-ends-with')
+ $newCardInfo = $paymentInfoPanel.find('.new-card-info')
+ $freeJamTrackPrompt = $screen.find('.payment-prompt.free-jamtrack')
+ $noFreeJamTrackPrompt = $screen.find('.payment-prompt.no-free-jamtrack')
+
if($screen.length == 0) throw "$screen must be specified";
if($navigation.length == 0) throw "$navigation must be specified";
diff --git a/web/app/assets/javascripts/checkout_signin.js b/web/app/assets/javascripts/checkout_signin.js
index 26fb3a1ed..22ee88060 100644
--- a/web/app/assets/javascripts/checkout_signin.js
+++ b/web/app/assets/javascripts/checkout_signin.js
@@ -51,7 +51,7 @@
}
function moveNext() {
- window.location = '/client#/checkout_payment';
+ window.location = '/client#/checkoutPayment';
return false;
}
@@ -69,7 +69,7 @@
rest.login({email: email, password: password, remember_me: true})
.done(function() {
- window.location = '/client#/checkout_payment'
+ window.location = '/client#/checkoutPayment'
window.location.reload();
})
.fail(function(jqXHR) {
@@ -106,7 +106,7 @@
'beforeShow': beforeShow,
'afterShow': afterShow
};
- app.bindScreen('checkout_signin', screenBindings);
+ app.bindScreen('checkoutSignin', screenBindings);
$screen = $("#checkoutSignInScreen");
$navigation = $screen.find(".checkout-navigation-bar");
diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js
index b8c0f62d5..01b7cf326 100644
--- a/web/app/assets/javascripts/everywhere/everywhere.js
+++ b/web/app/assets/javascripts/everywhere/everywhere.js
@@ -215,13 +215,7 @@
}
function initShoppingCart(app) {
-
- var user = app.user()
- if(user) {
- user.done(function(userProfile) {
- context.JK.JamTrackUtils.checkShoppingCart();
- })
- }
+ context.JK.JamTrackUtils.checkShoppingCart();
}
})(window, jQuery);
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index a5ca73b2f..0eda8f103 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -473,7 +473,7 @@
processData: false,
contentType: 'application/json',
data: JSON.stringify(options)
- });
+ })
}
function getUserDetail(options) {
@@ -1547,10 +1547,10 @@
});
}
- function placeOrder(options) {
+ function placeOrder() {
return $.ajax({
type: "POST",
- url: '/api/recurly/place_order?' + $.param(options),
+ url: '/api/recurly/place_order',
dataType: "json",
contentType: 'application/json'
});
diff --git a/web/app/assets/javascripts/jam_track_utils.js.coffee b/web/app/assets/javascripts/jam_track_utils.js.coffee
index 8ba73f592..d3da3180a 100644
--- a/web/app/assets/javascripts/jam_track_utils.js.coffee
+++ b/web/app/assets/javascripts/jam_track_utils.js.coffee
@@ -22,7 +22,12 @@ class JamTrackUtils
else
cartLink.addClass("hidden")
-
+ compareAddress: (billing, shipping) =>
+ billing.address1 == shipping.address1 &&
+ billing.address2 == shipping.address2 &&
+ billing.zip == shipping.zip &&
+ billing.city == shipping.city &&
+ billing.country == shipping.country;
# global instance
diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js
index 8fb111bab..b4f545723 100644
--- a/web/app/assets/javascripts/order.js
+++ b/web/app/assets/javascripts/order.js
@@ -115,19 +115,6 @@
purchasedJamTrackIterator = 0;
}
- function beforeHide() {
- if(downloadJamTracks) {
- context._.each(downloadJamTracks, function(downloadJamTrack) {
- downloadJamTrack.destroy();
- downloadJamTrack.root.remove();
- })
-
- downloadJamTracks = [];
- }
- purchasedJamTracks = null;
- purchasedJamTrackIterator = 0;
- }
-
// TODO: Refactor: this function is long and fraught with many return points.
function next(e) {
e.preventDefault();
diff --git a/web/app/assets/javascripts/selectLocation.js b/web/app/assets/javascripts/selectLocation.js
index 4d5b36711..93cde1b12 100644
--- a/web/app/assets/javascripts/selectLocation.js
+++ b/web/app/assets/javascripts/selectLocation.js
@@ -6,7 +6,7 @@
context.JK.SelectLocation = Class.extend({
- init: function ($countries, $regions, $cities, app) {
+ init: function ($countries, $regions, $cities, app, useEasyDropdown) {
this.api = context.JK.Rest();
this.logger = context.JK.logger;
this.loadingCitiesData = false;
@@ -14,9 +14,12 @@
this.loadingCountriesData = false;
this.nilOptionStr = '';
this.nilOptionText = 'n/a';
+ this.countriesLoaded = false;
this.$countries = $countries;
this.$regions = $regions;
this.$cities = $cities;
+ this.$deferred = null;
+ this.useEasyDropdown = useEasyDropdown === undefined ? true : useEasyDropdown;
this.app = app;
$countries.on('change', function (evt) {
@@ -24,11 +27,24 @@
this.handleCountryChanged();
return false;
}.bind(this));
- $regions.on('change', function (evt) {
- evt.stopPropagation();
- this.handleRegionChanged();
- return false;
- }.bind(this));
+ if($regions) {
+ $regions.on('change', function (evt) {
+ evt.stopPropagation();
+ this.handleRegionChanged();
+ return false;
+ }.bind(this));
+ }
+ },
+ selectCountry: function (country) {
+ if(this.useEasyDropdown) {
+ this.$countries.easyDropDown('select', country, true)
+ }
+ else {
+ this.$countries.val(country)
+ }
+ },
+ ready: function() {
+ return this.$deferred;
},
load: function (country, region, city) {
@@ -42,13 +58,9 @@
country = 'US';
}
- this.loadingCountriesData = true;
- this.loadingRegionsData = true;
- this.loadingCitiesData = true;
-
// make the 3 slower requests, which only matter if the user wants to affect their ISP or location
-
- this.api.getCountries()
+ this.loadingCountriesData = true;
+ this.$deferred = this.api.getCountries()
.done(function (countriesx) {
this.populateCountriesx(countriesx["countriesx"], country);
}.bind(this))
@@ -57,7 +69,9 @@
this.loadingCountriesData = false;
}.bind(this))
- if (country) {
+
+ if (country && this.$regions) {
+ this.loadingRegionsData = true;
this.api.getRegions({ country: country })
.done(function (regions) {
this.populateRegions(regions["regions"], region);
@@ -67,7 +81,8 @@
this.loadingRegionsData = false;
}.bind(this))
- if (region) {
+ if (region && this.$cities) {
+ this.loadingCitiesData = true;
this.api.getCities({ country: country, region: region })
.done(function (cities) {
this.populateCities(cities["cities"], this.city)
@@ -78,9 +93,15 @@
}.bind(this))
}
}
+ return this.$deferred;
},
handleCountryChanged: function () {
var selectedCountry = this.$countries.val()
+
+ if(!this.$regions) {
+ return;
+ }
+
var selectedRegion = this.$regions.val()
var cityElement = this.$cities
@@ -144,7 +165,9 @@
else {
cityElement.children().remove();
cityElement.append($(this.nilOptionStr).text(this.nilOptionText));
- context.JK.dropdown(cityElement);
+ if(this.useEasyDropdown) {
+ context.JK.dropdown(cityElement);
+ }
}
},
@@ -159,7 +182,7 @@
if (!countryx.countrycode) return;
var option = $(this.nilOptionStr);
- option.text(countryx.countryname);
+ option.text(countryx.countryname ? countryx.countryname : countryx.countrycode);
option.attr("value", countryx.countrycode);
if (countryx.countrycode == this.country) {
@@ -170,6 +193,8 @@
},
populateCountriesx: function (countriesx) {
+ this.countriesLoaded = true;
+
// countriesx has the format [{countrycode: "US", countryname: "United States"}, ...]
this.foundCountry = false;
@@ -194,8 +219,9 @@
this.$countries.val(this.country);
this.$countries.attr("disabled", null).easyDropDown('enable');
-
- context.JK.dropdown(this.$countries);
+ if(this.useEasyDropdown) {
+ context.JK.dropdown(this.$countries);
+ }
},
writeRegion: function (index, region) {
@@ -220,7 +246,9 @@
this.$regions.val(userRegion)
this.$regions.attr("disabled", null).easyDropDown('enable');
- context.JK.dropdown(this.$regions);
+ if(this.useEasyDropdown) {
+ context.JK.dropdown(this.$regions);
+ }
},
writeCity: function (index, city) {
@@ -245,7 +273,9 @@
this.$cities.val(userCity)
this.$cities.attr("disabled", null).easyDropDown('enable');
- context.JK.dropdown(this.$cities);
+ if(this.useEasyDropdown) {
+ context.JK.dropdown(this.$cities);
+ }
},
regionListFailure: function (jqXHR, textStatus, errorThrown) {
diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js
index d8856d520..eb782bd56 100644
--- a/web/app/assets/javascripts/shopping_cart.js
+++ b/web/app/assets/javascripts/shopping_cart.js
@@ -30,15 +30,15 @@
e.preventDefault();
if (!context.JK.currentUserId) {
- window.location = '/client#/checkout_signin';
+ window.location = '/client#/checkoutSignin';
}
else {
app.user().done(function(user) {
if(user.reuse_card) {
- window.location = '/client#/checkout_order';
+ window.location = '/client#/checkoutOrder';
}
else {
- window.location = '/client#/checkout_payment';
+ window.location = '/client#/checkoutPayment';
}
})
diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss
index 5e3128cf1..7d5190ad8 100644
--- a/web/app/assets/stylesheets/client/checkout.css.scss
+++ b/web/app/assets/stylesheets/client/checkout.css.scss
@@ -64,88 +64,3 @@
}
}
}
-
-.order-panel {
- padding: 30px;
-
- .order-header {
- h2 {
- font-size: 16px;
- }
- }
-
- .order-content {
- margin-top: 20px;
- }
-
- .order-left-page {
- float: left;
- width: 60%;
-
- .payment-info-page {
- padding: 5px;
-
- .info-caption-link {
- .caption-text {
- float: left;
- }
- .caption-link {
- float: left;
- margin-left: 5px;
- }
- }
-
- .address-info {
- width: 50%;
- float: left;
- }
-
- .payment-method-info {
- width: 50%;
- float: left;
- }
- }
- .order-items-page {
- padding: 5px;
-
- .cart-item-caption {
- width: 50%;
- text-align: left;
- float: left;
- }
-
- .cart-item-caption#header {
- font-weight: bold;
- }
-
- .cart-item-price {
- width: 25%;
- text-align: right;
- float: left;
- }
-
- .cart-item-quantity {
- width: 25%;
- text-align: right;
- float: left;
- }
-
- .cart-items {
- margin-top: 10px;
- }
-
- .cart-item {
- margin-top: 10px;
- }
- }
- }
- .order-right-page {
- float: right;
- width: 35%;
- text-align: center;
-
- .order-total {
- color: #ed3618;
- }
- }
-}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/checkout_order.css.scss b/web/app/assets/stylesheets/client/checkout_order.css.scss
new file mode 100644
index 000000000..9059198c4
--- /dev/null
+++ b/web/app/assets/stylesheets/client/checkout_order.css.scss
@@ -0,0 +1,204 @@
+@import "client/common.css.scss";
+#checkoutOrderScreen {
+
+
+ p {
+ font-size:12px;
+ margin:0;
+ }
+
+ .payment-prompt {
+ color:white;
+ line-height:125%;
+ }
+
+ h2 {
+ color:white;
+ background-color:#4d4d4d;
+ font-weight:normal;
+ margin: 0 0 10px 0;
+ font-size:14px;
+ padding: 3px 0 3px 10px;
+ height: 14px;
+ line-height: 14px;
+ vertical-align: middle;
+ text-align:left;
+ }
+
+
+
+ .action-bar {
+ margin-top:20px;
+ }
+
+ .line {
+ margin:10px 0 10px;
+ border-width:0 0 1px 0;
+ border-color:#ccc;
+ border-style:solid;
+ }
+
+ #checkout-info-help {
+ margin-right:1px;
+ }
+
+ .billing-info-item {
+ margin-bottom:3px;
+ }
+
+ .country {
+ margin-left:15px;
+ }
+ .billing-address {
+ margin-bottom:20px;
+ }
+ .order-panel {
+ padding: 30px;
+ min-width:730px;
+
+ .place-order {
+ font-size: 14px;
+ padding: 1px 3px;
+ line-height: 15px;
+ }
+
+ .place-order-center {
+ text-align:center;
+ margin:20px 0 20px;
+ }
+
+ .change-payment-info {
+ position:absolute;
+ font-size:12px;
+ left:180px;
+ }
+
+ .billing-caption {
+ margin-bottom:5px;
+ float:left;
+ position:relative;
+ }
+ .order-header {
+ h2 {
+ font-size: 16px;
+ }
+ }
+
+ .shipping-address {
+ display:none;
+ }
+
+ .order-help {
+ margin:20px 0 30px;
+ }
+ .order-summary {
+ padding:0 20px;
+
+ .billing-caption {
+ float:none;
+ margin-bottom:10px;
+ }
+ }
+ .order-items-header {
+ float:left;
+ margin-bottom:5px;
+ }
+
+ .order-items-value {
+ float:right;
+ }
+
+ .order-content {
+ margin-top: 20px;
+ background-color:#262626;
+ }
+
+ .order-left-page {
+ float: left;
+ width: 65%;
+ background-color:#262626;
+ border-width:0 1px 0 0;
+ border-style:solid;
+ border-color:#333;
+ @include border_box_sizing;
+
+ .payment-info-page {
+
+ .info-caption-link {
+ .caption-text {
+ float: left;
+ }
+ .caption-link {
+ float: left;
+ margin-left: 5px;
+ }
+ }
+
+ .address-info {
+ width: 50%;
+ float: left;
+ padding:0 10px;
+ @include border_box_sizing;
+ margin-bottom:30px;
+ }
+
+ .payment-method-info {
+ width: 50%;
+ float: left;
+ padding:0 10px;
+ @include border_box_sizing;
+ }
+ }
+ .order-items-page {
+ .cart-item-caption {
+ width: 50%;
+ text-align: left;
+ float: left;
+ margin-bottom:10px;
+ @include border_box_sizing;
+ }
+
+ .cart-item-price {
+ width: 25%;
+ text-align: right;
+ float: left;
+ padding:0 10px;
+ margin-bottom:10px;
+ @include border_box_sizing;
+ }
+
+ .cart-item-quantity {
+ width: 10%;
+ text-align: right;
+ float: left;
+ padding:0 10px;
+ margin-bottom:10px;
+ @include border_box_sizing;
+ }
+
+ .cart-items {
+ margin-top: 10px;
+ padding-left:10px;
+ }
+
+ .cart-item {
+ margin-top: 10px;
+ }
+
+ .no-cart-items {
+ }
+ }
+ }
+ .order-right-page {
+ float: left;
+ width: 35%;
+ text-align: left;
+ background-color:#262626;
+ @include border_box_sizing;
+
+ .order-total {
+ color: #ed3618;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/checkout_payment.css.scss b/web/app/assets/stylesheets/client/checkout_payment.css.scss
index 0bec39083..97c17c359 100644
--- a/web/app/assets/stylesheets/client/checkout_payment.css.scss
+++ b/web/app/assets/stylesheets/client/checkout_payment.css.scss
@@ -3,6 +3,7 @@
.payment-wrapper {
padding:10px 30px;
+ min-width:600px;
}
p {
@@ -41,6 +42,12 @@
input[type="text"], input[type="password"] {
width: 90%;
+ @include border_box_sizing;
+ }
+
+ select#billing-country {
+ width:90%;
+ @include border_box_sizing;
}
&.signed-in {
@@ -53,6 +60,21 @@
}
}
}
+ &.not-signed-in {
+ .row.second {
+ .left-side {
+ display:none;
+ }
+ .right-side {
+ width:100%;
+ }
+ }
+ }
+
+ #divShippingFirstName, #divShippingLastName {
+ display:none;
+ }
+
.row {
margin-top:20px;
@@ -87,6 +109,23 @@
float: left;
@include border_box_sizing;
}
+
+ div.terms-of-service.ichecbuttons {
+ margin-left:5px;
+ .icheckbox_minimal {
+
+ float: left;
+ display: block;
+ margin: 5px 5px 0 0;
+ }
+ }
+ .terms-of-service-label-holder {
+ font-size:12px;
+ line-height:18px;
+ top:4px;
+ position:relative;
+ float:left;
+ }
}
.hint {
@@ -131,11 +170,20 @@
@include border_box_sizing;
}
- .save-card-checkbox {
+ .save-card-checkbox, .reuse-existing-card-checkbox {
float:left;
display:block;
margin-right:5px;
}
+
+ label[for="reuse-existing-card"], label[for="save-card"] {
+ line-height: 18px;
+ vertical-align: middle;
+ }
+
+ .reuse-existing-card-helper {
+ margin-bottom:10px;
+ }
}
.shipping-address {
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index 73fbaf592..4f94cb2b4 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -55,6 +55,7 @@
*= require ./checkout
*= require ./checkout_signin
*= require ./checkout_payment
+ *= require ./checkout_order
*= require ./genreSelector
*= require ./sessionList
*= require ./searchResults
diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss
index fcfca9f13..69f430b7f 100644
--- a/web/app/assets/stylesheets/client/common.css.scss
+++ b/web/app/assets/stylesheets/client/common.css.scss
@@ -12,6 +12,7 @@ $ColorLinkHover: #82AEAF;
$ColorSidebarText: #a0b9bd;
$ColorScreenBackground: lighten($ColorUIBackground, 10%);
$ColorTextBoxBackground: #c5c5c5;
+$ColorTextBoxDisabledBackground: #999;
$ColorRecordingBackground: #471f18;
$ColorTextHighlight: white;
diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss
index 9bee28a72..7fd3bd175 100644
--- a/web/app/assets/stylesheets/client/content.css.scss
+++ b/web/app/assets/stylesheets/client/content.css.scss
@@ -222,6 +222,10 @@
border:none;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
+
+ &:disabled {
+ background-color: $ColorTextBoxDisabledBackground;
+ }
}
}
diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss
index ab1444769..2aa254437 100644
--- a/web/app/assets/stylesheets/client/jamkazam.css.scss
+++ b/web/app/assets/stylesheets/client/jamkazam.css.scss
@@ -322,6 +322,10 @@ input[type="text"], input[type="password"]{
border:none;
padding:3px;
font-size:15px;
+
+ &:disabled {
+ background-color: $ColorTextBoxDisabledBackground;
+ }
}
textarea {
diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb
index d5b7fbaec..28ef551eb 100644
--- a/web/app/controllers/api_recurly_controller.rb
+++ b/web/app/controllers/api_recurly_controller.rb
@@ -8,10 +8,18 @@ class ApiRecurlyController < ApiController
def create_account
billing_info = params[:billing_info]
+ shipping_info = params[:shipping_info]
+ # should we let the user reuse this card next time?
+ reuse_card_next_time = params[:reuse_card_next_time] == "true"
+ # should we update the card info, or use what's on file this time?
+ reuse_card_this_time = params[:reuse_card_this_time] == "true"
+ # terms of service accepted?
+ terms_of_service = params[:terms_of_service] == "true"
+
if current_user
# keep reuse card up-to-date
- User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card])
+ User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card_next_time])
else
options = {
remote_ip: request.remote_ip,
@@ -20,17 +28,17 @@ class ApiRecurlyController < ApiController
email: params[:email],
password: params[:password],
password_confirmation: params[:password],
- terms_of_service: params[:terms_of_service],
- instruments: [{ :instrument_id => 'other', :proficiency_level => 3, :priority => 1 }],
+ terms_of_service: terms_of_service,
+ instruments: [{ :instrument_id => 'other', :proficiency_level => 1, :priority => 1 }],
birth_date: nil,
location: { :country => billing_info[:country], :state => billing_info[:state], :city => billing_info[:city]},
musician: true,
- recaptcha_failed: false,
+ skip_recaptcha: true,
invited_user: nil,
fb_signup: nil,
signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm",
any_user: any_user,
- reuse_card: params[:reuse_card]
+ reuse_card: reuse_card_next_time
}
user = UserManager.new.signup(options)
@@ -45,7 +53,14 @@ class ApiRecurlyController < ApiController
end
begin
- @account = @client.find_or_create_account(current_user, billing_info)
+ billing_info[:ip_address] = request.remote_ip if billing_info
+ if reuse_card_this_time
+ # do not attempt to update any billing/shipping info unless the user re-inputs their info!
+ @account = @client.get_account(current_user)
+ else
+ @account = @client.find_or_create_account(current_user, billing_info)
+ end
+
render :json=>account_json(@account)
rescue RecurlyClientError => x
render json: { :message => x.inspect, errors: x.errors }, :status => 404
@@ -61,7 +76,7 @@ class ApiRecurlyController < ApiController
# get Recurly account
def get_account
- @account=@client.get_account(current_user)
+ @account = @client.get_account(current_user)
render :json=>account_json(@account)
rescue RecurlyClientError => e
@@ -79,9 +94,11 @@ class ApiRecurlyController < ApiController
# get Billing Information
def billing_info
@account = @client.get_account(current_user)
- # @billing = @account.billing_info
- # @billing ||= @account
- render :json=> account_json(@account)
+ if @account
+ render :json=> account_json(@account)
+ else
+ render :json=> {}, :status => 404
+ end
rescue RecurlyClientError => x
render json: { message: x.inspect, errors: x.errors}, :status => 404
end
@@ -96,33 +113,20 @@ class ApiRecurlyController < ApiController
def place_order
error=nil
- puts "PLACING ORDER #{params.inspect}"
response = {jam_tracks:[]}
- # 1st confirm that all specified JamTracks exist
- jam_tracks = []
+ current_user.shopping_carts.each do |shopping_cart|
+ jam_track = shopping_cart.cart_product
- params[:jam_tracks].each do |jam_track_id|
- jam_track = JamTrack.where("id=?", jam_track_id).first
- if jam_track
- jam_tracks << jam_track
- else
- error="JamTrack not found for '#{jam_track_id}'"
- break
- end
+ # if shopping_cart has any marked_for_redeem, then we zero out the price by passing in 'free'
+ # NOTE: shopping_carts have the idea of quantity, but you should only be able to buy at most one JamTrack. So anything > 0 is considered free for a JamTrack
+
+ jam_track_right = @client.place_order(current_user, jam_track, shopping_cart)
+ # build up the response object with JamTracks that were purchased.
+ # if this gets more complicated, we should switch to RABL
+ response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
end
- # then buy each
- unless error
- jam_tracks.each do |jam_track|
- jam_track_right = @client.place_order(current_user, jam_track)
- # build up the response object with JamTracks that were purchased.
- # if this gets more complicated, we should switch to RABL
- response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
- end
- end
-
-
if error
render json: { errors: {message:error}}, :status => 404
else
@@ -138,16 +142,21 @@ private
end
def account_json(account)
+
+ billing_info = account.billing_info.nil? ? nil : {
+ :first_name => account.billing_info.first_name,
+ :last_name => account.billing_info.last_name,
+ :address1 => account.billing_info.address1,
+ :address2 => account.billing_info.address2,
+ :city => account.billing_info.city,
+ :state => account.billing_info.state,
+ :zip => account.billing_info.zip,
+ :country => account.billing_info.country,
+ :last_four => account.billing_info.last_four
+ }
+
{
- :first_name => account.first_name,
- :last_name => account.last_name,
- :email => account.email,
- :address1 => account.billing_info ? account.billing_info.address1 : nil,
- :address2 => account.billing_info ? account.billing_info.address2 : nil,
- :city => account.billing_info ? account.billing_info.city : nil,
- :state => account.billing_info ? account.billing_info.state : nil,
- :zip => account.billing_info ? account.billing_info.zip : nil,
- :country => account.billing_info ? account.billing_info.country : nil
+ billing_info: billing_info
}
end
diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb
index 3bb02701f..bfc3eb750 100644
--- a/web/app/controllers/api_shopping_carts_controller.rb
+++ b/web/app/controllers/api_shopping_carts_controller.rb
@@ -16,7 +16,11 @@ class ApiShoppingCartsController < ApiController
raise StateError, "Invalid JamTrack."
end
- @cart = ShoppingCart.create any_user, jam_track
+ ShoppingCart.transaction do
+ mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
+ @cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
+ end
+
if @cart.errors.any?
response.status = :unprocessable_entity
@@ -46,7 +50,21 @@ class ApiShoppingCartsController < ApiController
@cart = any_user.shopping_carts.find_by_id(params[:id])
raise StateError, "Invalid Cart." if @cart.nil?
- @cart.destroy
+ ShoppingCart.transaction do
+ @cart.destroy
+
+ # check if we should move the redemption
+ mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
+
+ carts = any_user.shopping_carts
+
+ # if we find any carts on the account, mark one redeemable
+ if mark_redeem && carts.length > 0
+ carts[0].redeem(mark_redeem)
+ carts[0].save
+ end
+ end
+
respond_with responder: ApiResponder, :status => 204
end
diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl
index af67eb49b..5366cf42f 100644
--- a/web/app/views/api_users/show.rabl
+++ b/web/app/views/api_users/show.rabl
@@ -17,6 +17,10 @@ if @user == current_user
geoiplocation.info if geoiplocation
end
+ node :free_jamtrack do |user|
+ Rails.application.config.one_free_jamtrack_per_user && user.has_redeemable_jamtrack
+ end
+
node :mods do |user|
user.mods_json
end
diff --git a/web/app/views/clients/_checkout_order.html.slim b/web/app/views/clients/_checkout_order.html.slim
new file mode 100644
index 000000000..dbd944cce
--- /dev/null
+++ b/web/app/views/clients/_checkout_order.html.slim
@@ -0,0 +1,154 @@
+div layout="screen" layout-id="checkoutOrder" id="checkoutOrderScreen" class="screen secondary"
+ .content
+ .content-head
+ .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19})
+ h1 check out
+ = render "screen_navigation"
+ .content-body
+ #order_error.error.hidden
+ .content-body-scroller
+ .content-wrapper
+ .checkout-navigation-bar
+ .order-panel
+ .payment-wrapper
+ p.payment-prompt.hidden
+ | Please review your order, and if everything looks correct, click the PLACE YOUR ORDER button. Thank you!
+ p.empty-cart-prompt.hidden
+ | You have nothing in your cart. You can go browse for JamTracks
+ a href="/client#/jamtrack" here
+ | .
+ .order-content
+
+
+ .clearall
+ .action-bar
+
+ .right
+ a.button-grey href="#" id="checkout-info-help" HELP
+ a.button-grey.back href="#" BACK
+ a.button-orange.place-order href="#" PLACE YOUR ORDER
+ .clearall
+ .thanks-panel
+ h2 Thank you for your order!
+ br
+ .thanks-detail We'll send you an email confirming your order shortly.
+ br
+ .thanks-detail.jam-tracks-in-browser.hidden
+ | To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session.
+ .thanks-detail.purchased-jam-track.hidden
+ h2.purchased-jam-track-header Downloading Your Purchased JamTracks
+ span Each JamTrack will be downloaded sequentially.
+ br
+ span.notice Note that you do not have to wait for this to complete in order to use your JamTrack later.
+ br.clear
+ ul.purchased-list
+
+
+
+script type='text/template' id='template-order-content'
+ .order-left-page
+ .payment-info-page
+ h2 ADDRESS & PAYMENT
+ .address-info
+ .billing-address
+ .billing-caption
+ | BILLING ADDRESS:
+ a.change-payment-info href="#" change
+ .clearall
+ .billing-info-item= "{{data.billing_info.address1}}"
+ .billing-info-item= "{{data.billing_info.address2}}"
+ .billing-info-item
+ | {{data.billing_info.city}}, {{data.billing_info.state}} {{data.billing_info.zip}}
+ span.country= "{{data.billing_info.country}}"
+ .shipping-address
+ .billing-caption
+ | SHIPPING ADDRESS:
+ a.change-payment-info href="#" change
+ .clearall
+ = "{% if (data.shipping_as_billing) { %}"
+ .billing-info-item Same as billing address
+ = "{% } else { %}"
+ .billing-info-item= "{{data.shipping_info.address1}}"
+ .billing-info-item= "{{data.shipping_info.address2}}"
+ .billing-info-item
+ | {{data.shipping_info.city}}, {{data.shipping_info.state}} {{data.shipping_info.zip}}
+ span.country= "{{data.shipping_info.country}}"
+ = "{% } %}"
+ br
+ .payment-method-info
+ .billing-caption
+ | PAYMENT METHOD:
+ a.change-payment-info href="#" change
+ .clearall
+
+ /= image_tag ''
+ ="Credit card ending {{data.billing_info.last_four}}"
+
+ .clearall
+ .order-items-page
+ h2 ORDER DETAILS
+ .cart-items
+ .cart-item-caption
+ span YOUR ORDER INCLUDES:
+ .cart-item-price
+ span PRICE
+ .cart-item-quantity
+ span QUANTITY
+ .clearall
+ = "{% if (data.carts.length == 0) { %}"
+ .no-cart-items You have no orders now.
+ = "{% } %}"
+ = "{% _.each(data.carts, function(cart) { %}"
+ .cart-item cart-id="{{cart.id}}"
+ .cart-item-caption
+ = "{{cart.cart_type}}: {{cart.product_info.name}}"
+ .cart-item-price
+ = "$ {{Number(cart.product_info.total_price).toFixed(2)}}"
+ .cart-item-quantity
+ = "{{cart.quantity}}"
+ .clearall
+ = "{% }); %}"
+ .clearall
+ .order-right-page
+ h2 PLACE ORDER
+ .recurly-data.hidden
+ = "{% _.each(data.carts, function(cart) { %}"
+ .plan data-plan-code="{{cart.product_info.plan_code}}"
+ input data-recurly="plan" type="text" value="{{cart.product_info.plan_code}}"
+ = "{% }); %}"
+ .order-summary
+ .place-order-center
+ a.button-orange.place-order href="#" PLACE YOUR ORDER
+ .clearall
+
+ .billing-caption ORDER SUMMARY:
+ .order-items-header Order items:
+ .order-items-value ${{data.sub_total}}
+ .clearall
+ .order-items-header Shipping & handling:
+ .order-items-value $0.00
+ .clearall
+ .line
+ .order-items-header Total before tax:
+ .order-items-value ${{data.sub_total}}
+ .clearall
+ .order-items-header.taxes Taxes:
+ .order-items-value.taxes Calculating...
+ .clearall
+ .line
+ .order-items-header.order-total Order total:
+ .order-items-value.order-total Calculating...
+ .clearall
+ .order-help
+ span By placing your order, you agree to JamKazam's
+ '
+ a href="http://www.jamkazam.com/corp/terms" rel="external" terms of service
+ '
+ span and
+ '
+ a href="http://www.jamkazam.com/corp/returns" rel="external" returns policy
+ span .
+ .clearall
+
+script type='text/template' id='template-purchased-jam-track'
+ li data-jam-track-id="{{data.jam_track_id}}"
\ No newline at end of file
diff --git a/web/app/views/clients/_checkout_payment.html.slim b/web/app/views/clients/_checkout_payment.html.slim
index 2b8b3862f..3ea9cb831 100644
--- a/web/app/views/clients/_checkout_payment.html.slim
+++ b/web/app/views/clients/_checkout_payment.html.slim
@@ -1,20 +1,22 @@
-div layout="screen" layout-id="checkout_payment" id="checkoutPaymentScreen" class="screen secondary no-login-required"
+div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class="screen secondary no-login-required"
.content
.content-head
.content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19})
h1 check out
= render "screen_navigation"
.content-body
- #order_error.error.hidden
+ #payment_error.error.hidden
.content-body-scroller
.content-wrapper
.checkout-navigation-bar
.payment-wrapper
- p.payment-prompt
- | Please enter you billing address and payment information below. You will not be billed for your first JamTrack, which is 100% free.
+ p.payment-prompt.free-jamtrack.hidden
+ | Please enter your billing address and payment information below. You will not be billed for your first JamTrack, which is 100% free.
| But we need this data to prevent fraud/abuse of those who would create multiple accounts to collect multiple free JamTracks.
| You will not be billed for any charges of any kind without your explicit authorization.
| There are no "hidden" charges or fees, thank you!
+ p.payment-prompt.no-free-jamtrack.hidden
+ | Please enter your billing address and payment information below.
form class="payment-info" id="checkout-payment-info"
.row.first
@@ -67,37 +69,49 @@ div layout="screen" layout-id="checkout_payment" id="checkoutPaymentScreen" clas
.billing-label
label for="billing-country" Country:
.billing-value
- input type="text" id="billing-country"
+ select id="billing-country"
+ option value="US" US
.clearall
.right-side
.payment-method
h2.payment-method-caption PAYMENT METHOD
- #divCardName.field
+ .new-card-info
+ #divCardName.field.hidden
+ .card-label
+ label for="card-name" Name of Card:
+ .card-value
+ input type="text" id="card-name"
+ .clearall
+ #divCardNumber.field
+ .card-label
+ label for="card-number" Card Number:
+ .card-value
+ input type="text" id="card-number"
+ .clearall
+ #divCardExpiry.field
+ .card-label Expiration Date:
+ .card-value
+ =date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html => {:class => "account-profile-birthdate", :id => "card-expiry"})
+ .clearall
+ #divCardVerify.field
+ .card-label
+ label for="card-verify"
+ | CVV Code:
+ .hint.cvv
+ | (back of card)
+ .card-value
+ input type="text" id="card-verify"
+ .clearall
+ .reuse-existing-card
.card-label
- label for="card-name" Name of Card:
.card-value
- input type="text" id="card-name"
- .clearall
- #divCardNumber.field
- .card-label
- label for="card-number" Card Number:
- .card-value
- input type="text" id="card-number"
- .clearall
- #divCardExpiry.field
- .card-label Expiration Date:
- .card-value
- =date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html => {:class => "account-profile-birthdate", :id => "card-expiry"})
- .clearall
- #divCardVerify.field
- .card-label
- label for="card-verify"
- | CVV Code:
- .hint.cvv
- | (back-of-card)
- .card-value
- input type="text" id="card-verify"
- .clearall
+ .reuse-existing-card-checkbox.ichecbuttons
+ input type="checkbox" id="reuse-existing-card" name="reuse-existing-card" checked="checked"
+ .reuse-existing-card-helper
+ label for="reuse-existing-card"
+ | Use card ending in
+ span.existing-card-ends-with
+ .clearall
.card-label
.card-value
.save-card-checkbox.ichecbuttons
@@ -108,7 +122,7 @@ div layout="screen" layout-id="checkout_payment" id="checkoutPaymentScreen" clas
.clearall
.clearall
.row.second
- .left-side
+ left-side.hidden
.shipping-address
h2.shipping-address-label SHIPPING ADDRESS
.shipping-as-billing.ichecbuttons
@@ -180,6 +194,14 @@ div layout="screen" layout-id="checkout_payment" id="checkoutPaymentScreen" clas
.account-value
input name="password" type="password"
.clearall
+ #divJamKazamTos.field
+ .terms-of-service.ichecbuttons
+ input type="checkbox" name="terms-of-service"
+ .terms-of-service-label-holder
+ label for="terms-of-service"
+ | I have read and agree to the JamKazam
+ a rel="external" href=corp_terms_path terms of service
+ .clearall
.clearall
diff --git a/web/app/views/clients/_checkout_signin.html.slim b/web/app/views/clients/_checkout_signin.html.slim
index e0f089326..d6dd4ccdb 100644
--- a/web/app/views/clients/_checkout_signin.html.slim
+++ b/web/app/views/clients/_checkout_signin.html.slim
@@ -1,4 +1,4 @@
-div layout="screen" layout-id="checkout_signin" id="checkoutSignInScreen" class="screen secondary no-login-required"
+div layout="screen" layout-id="checkoutSignin" id="checkoutSignInScreen" class="screen secondary no-login-required"
.content
.content-head
.content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19})
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 82e1d18ba..5c426bc1a 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -41,6 +41,7 @@
<%= render "shopping_cart" %>
<%= render "checkout_signin" %>
<%= render "checkout_payment" %>
+<%= render "checkout_order" %>
<%= render "order" %>
<%= render "feed" %>
<%= render "bands" %>
@@ -241,7 +242,10 @@
checkoutSignInScreen.initialize();
var checkoutPaymentScreen = new JK.CheckoutPaymentScreen(JK.app);
- checkoutPaymentScreen.initialize();
+ checkoutPaymentScreen.initialize();
+
+ var checkoutOrderScreen = new JK.CheckoutOrderScreen(JK.app);
+ checkoutOrderScreen.initialize();
// var OrderScreen = new JK.OrderScreen(JK.app);
// OrderScreen.initialize();
diff --git a/web/app/views/layouts/client.html.erb b/web/app/views/layouts/client.html.erb
index 2c4238803..adee2a9d7 100644
--- a/web/app/views/layouts/client.html.erb
+++ b/web/app/views/layouts/client.html.erb
@@ -35,5 +35,7 @@
<%= yield %>
<%= render "shared/ga" %>
+ <%= render "shared/recurly" %>
+