GiftCards are purchable

This commit is contained in:
Seth Call 2015-11-29 13:58:10 -06:00
parent 6d514c3df2
commit bd02774db9
43 changed files with 949 additions and 239 deletions

View File

@ -312,4 +312,5 @@ track_downloads.sql
jam_track_lang_idx.sql
giftcard.sql
add_description_to_crash_dumps.sql
acappella.sql
acappella.sql
purchasable_gift_cards.sql

View File

@ -0,0 +1,24 @@
CREATE TABLE gift_card_types (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
card_type VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO gift_card_types (id, card_type) VALUES ('jam_tracks_5', 'jam_tracks_5');
INSERT INTO gift_card_types (id, card_type) VALUES ('jam_tracks_10', 'jam_tracks_10');
CREATE TABLE gift_card_purchases (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE SET NULL,
gift_card_type_id VARCHAR(64) REFERENCES gift_card_types(id) ON DELETE SET NULL,
recurly_adjustment_uuid VARCHAR(500),
recurly_adjustment_credit_uuid VARCHAR(500),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE sale_line_items ADD COLUMN gift_card_purchase_id VARCHAR(64) REFERENCES gift_card_purchases(id);

View File

@ -254,6 +254,8 @@ require "jam_ruby/models/band_search"
require "jam_ruby/import/tency_stem_mapping"
require "jam_ruby/models/jam_track_search"
require "jam_ruby/models/gift_card"
require "jam_ruby/models/gift_card_purchase"
require "jam_ruby/models/gift_card_type"
include Jampb

View File

@ -119,18 +119,16 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
end
def should_attribute_sale?(shopping_cart)
if shopping_cart.is_jam_track?
if created_within_affiliate_window(shopping_cart.user, Time.now)
product_info = shopping_cart.product_info
# 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
{fee_in_cents: (1.99 * 100 * real_quantity * rate.to_f).round}
else
false
end
if created_within_affiliate_window(shopping_cart.user, Time.now)
product_info = shopping_cart.product_info
# 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
{fee_in_cents: (product_info[:price] * 100 * real_quantity * rate.to_f).round}
else
raise 'shopping cart type not implemented yet'
false
end
end
def cumulative_earnings_in_dollars

View File

@ -15,10 +15,15 @@ module JamRuby
ShoppingCart.where(anonymous_user_id: @id).order('created_at DESC')
end
def destroy_all_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id)
end
def destroy_jam_track_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id, cart_type: JamTrack::PRODUCT_TYPE)
end
def admin
false
end

View File

@ -1,14 +1,15 @@
# represents the gift card you hold in your hand
module JamRuby
class GiftCard < ActiveRecord::Base
@@log = Logging.logger[GiftCard]
JAM_TRACKS_5 = 'jam_tracks_5'
JAM_TRACKS_10 = 'jam_tracks_10'
JAM_TRACKS_20 = 'jam_tracks_20'
CARD_TYPES =
[
JAM_TRACKS_10,
JAM_TRACKS_20
JAM_TRACKS_5,
JAM_TRACKS_10
]
@ -21,10 +22,10 @@ module JamRuby
def check_gifted
if user && user_id_changed?
if card_type == JAM_TRACKS_10
if card_type == JAM_TRACKS_5
user.gifted_jamtracks += 5
elsif card_type == JAM_TRACKS_10
user.gifted_jamtracks += 10
elsif card_type == JAM_TRACKS_20
user.gifted_jamtracks += 20
else
raise "unknown card type #{card_type}"
end

View File

@ -0,0 +1,17 @@
# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard)
module JamRuby
class GiftCardPurchase < ActiveRecord::Base
@@log = Logging.logger[GiftCardPurchase]
attr_accessible :user, :gift_card_type
def name
gift_card_type.sale_display
end
# who purchased the card?
belongs_to :user, class_name: "JamRuby::User"
belongs_to :gift_card_type, class_name: "JamRuby::GiftCardType"
end
end

View File

@ -0,0 +1,66 @@
# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard)
module JamRuby
class GiftCardType < ActiveRecord::Base
@@log = Logging.logger[GiftCardType]
PRODUCT_TYPE = 'GiftCardType'
JAM_TRACKS_5 = 'jam_tracks_5'
JAM_TRACKS_10 = 'jam_tracks_10'
CARD_TYPES =
[
JAM_TRACKS_5,
JAM_TRACKS_10
]
validates :card_type, presence: true, inclusion: {in: CARD_TYPES}
def self.jam_track_5
GiftCardType.find(JAM_TRACKS_5)
end
def self.jam_track_10
GiftCardType.find(JAM_TRACKS_10)
end
def name
sale_display
end
def price
if card_type == JAM_TRACKS_5
10.00
elsif card_type == JAM_TRACKS_10
20.00
else
raise "unknown card type #{card_type}"
end
end
def sale_display
if card_type == JAM_TRACKS_5
'JamTracks Gift Card (5)'
elsif card_type == JAM_TRACKS_10
'JamTracks Gift Card (10)'
else
raise "unknown card type #{card_type}"
end
end
def plan_code
if card_type == JAM_TRACKS_5
"jamtrack-giftcard-5"
elsif card_type == JAM_TRACKS_10
"jamtrack-giftcard-10"
else
raise "unknown card type #{card_type}"
end
end
def sales_region
'Worldwide'
end
end
end

View File

@ -155,6 +155,9 @@ module JamRuby
true
end
def sale_display
"JamTrack: " + name
end
def duplicate_positions?
counter = {}
jam_track_tracks.each do |track|

View File

@ -92,53 +92,15 @@ module JamRuby
transaction.save!
# now that we have the transaction saved, we also need to delete the jam_track_right if this is a refund, or voided
if transaction.transaction_type == 'refund' || transaction.transaction_type == 'void'
sale = Sale.find_by_recurly_invoice_id(transaction.invoice_id)
if sale && sale.is_jam_track_sale?
if sale.sale_line_items.length == 1
if sale.recurly_total_in_cents == transaction.amount_in_cents
line_item = sale.sale_line_items[0]
jam_track = line_item.product
jam_track_right = jam_track.right_for_user(transaction.user) if jam_track
if jam_track_right
line_item.affiliate_refunded = true
line_item.affiliate_refunded_at = Time.now
line_item.save!
if sale
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice",
body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks"
}).deliver
jam_track_right.destroy
# associate which JamTrack we assume this is related to in this one success case
transaction.jam_track = jam_track
transaction.save!
AdminMailer.recurly_alerts(transaction.user, {
subject: "NOTICE: #{transaction.user.email} has had JamTrack: #{jam_track.name} revoked",
body: "A #{transaction.transaction_type} event came from Recurly for sale with Recurly invoice ID #{sale.recurly_invoice_id}. We deleted their right to the track in our own database as a result."
}).deliver
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "NOTICE: #{transaction.user.email} got a refund, but unable to find JamTrackRight to delete",
body: "This should just mean the user already has no rights to the JamTrackRight when the refund came in. Not a big deal, but sort of weird..."
}).deliver
end
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} got a refund it was not for total value of a JamTrack sale",
body: "We received a #{transaction.transaction_type} notice for an amount that was not the same as the original sale. So, no action was taken in the database. sale total: #{sale.recurly_total_in_cents}, refund amount: #{transaction.amount_in_cents}"
}).deliver
end
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice with multiple JamTracks",
body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks"
}).deliver
end
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} has refund with no correlator to sales",

View File

@ -69,28 +69,6 @@ module JamRuby
}
end
def self.preview_invoice(current_user, shopping_carts)
line_items = {jam_tracks: []}
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
shopping_carts_jam_tracks << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
shopping_carts_subscriptions << shopping_cart
end
end
jam_track_items = preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks)
line_items[:jam_tracks] = jam_track_items if jam_track_items
# TODO: process shopping_carts_subscriptions
line_items
end
def self.ios_purchase(current_user, jam_track, receipt)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
@ -105,19 +83,14 @@ module JamRuby
def self.place_order(current_user, shopping_carts)
sales = []
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
shopping_carts_jam_tracks << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
shopping_carts_subscriptions << shopping_cart
end
if Sale.is_mixed(shopping_carts)
# the controller checks this too; this is just an extra-level of sanity checking
return sales
end
jam_track_sale = order_jam_tracks(current_user, shopping_carts_jam_tracks)
jam_track_sale = order_jam_tracks(current_user, shopping_carts)
sales << jam_track_sale if jam_track_sale
# TODO: process shopping_carts_subscriptions
@ -125,18 +98,10 @@ module JamRuby
sales
end
def self.preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks)
### XXX TODO;
# we currently use a fake plan in Recurly to estimate taxes using the Pricing.Attach metod in Recurly.js
# if we were to implement this the right way (ensure adjustments are on the account as necessary), then it would be better (more correct)
# just a pain to implement
end
def self.is_only_freebie(shopping_carts_jam_tracks)
def self.is_only_freebie(shopping_carts)
free = true
shopping_carts_jam_tracks.each do |cart|
shopping_carts.each do |cart|
free = cart.product_info[:free]
if !free
@ -162,7 +127,23 @@ module JamRuby
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
def self.order_jam_tracks(current_user, shopping_carts_jam_tracks)
def self.order_jam_tracks(current_user, shopping_carts)
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
shopping_carts_gift_cards = []
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
shopping_carts_jam_tracks << shopping_cart
elsif shopping_cart.is_gift_card?
shopping_carts_gift_cards << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
raise "unknown shopping cart type #{shopping_cart.cart_type}"
shopping_carts_subscriptions << shopping_cart
end
end
client = RecurlyClient.new
@ -171,8 +152,8 @@ module JamRuby
sale = create_jam_track_sale(current_user)
if sale.valid?
if is_only_freebie(shopping_carts_jam_tracks)
sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, nil)
if is_only_freebie(shopping_carts)
sale.process_shopping_carts(current_user, shopping_carts, nil)
sale.recurly_subtotal_in_cents = 0
sale.recurly_tax_in_cents = 0
@ -187,11 +168,13 @@ module JamRuby
return sale
end
sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents = 0
sale_line_item.recurly_total_in_cents = 0
sale_line_item.recurly_currency = 'USD'
sale_line_item.recurly_discount_in_cents = 0
sale.sale_line_items.each do |sale_line_item|
sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents = 0
sale_line_item.recurly_total_in_cents = 0
sale_line_item.recurly_currency = 'USD'
sale_line_item.recurly_discount_in_cents = 0
end
sale.save
else
@ -201,7 +184,7 @@ module JamRuby
purge_pending_adjustments(account)
created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
# now invoice the sale ... almost done
@ -257,13 +240,13 @@ module JamRuby
sale
end
def process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
def process_shopping_carts(current_user, shopping_carts, account)
created_adjustments = []
begin
shopping_carts_jam_tracks.each do |shopping_cart|
process_jam_track(current_user, shopping_cart, account, created_adjustments)
shopping_carts.each do |shopping_cart|
process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
end
rescue Recurly::Error, NoMethodError => x
# rollback any adjustments created if error
@ -279,7 +262,7 @@ module JamRuby
end
def process_jam_track(current_user, shopping_cart, account, created_adjustments)
def process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
recurly_adjustment_uuid = nil
recurly_adjustment_credit_uuid = nil
@ -287,15 +270,20 @@ module JamRuby
shopping_cart.reload
# get the JamTrack in this shopping cart
jam_track = shopping_cart.cart_product
cart_product = shopping_cart.cart_product
if jam_track.right_for_user(current_user)
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart)
return
if shopping_cart.is_jam_track?
jam_track = cart_product
if jam_track.right_for_user(current_user)
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart)
return
end
end
if account
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
adjustments = shopping_cart.create_adjustment_attributes(current_user)
@ -328,45 +316,69 @@ module JamRuby
# if the sale line item is invalid, blow up the transaction
unless sale_line_item.valid?
@log.error("sale item invalid! #{sale_line_item.errors.inspect}")
@@log.error("sale item invalid! #{sale_line_item.errors.inspect}")
puts("sale item invalid! #{sale_line_item.errors.inspect}")
Stats.write('web.recurly.purchase.sale_invalid', {message: sale_line_item.errors.to_s, value: 1})
raise RecurlyClientError.new(sale_line_item.errors)
end
# create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
jam_track_right.redeemed = shopping_cart.free?
end
if shopping_cart.is_jam_track?
jam_track = cart_product
# also if the purchase was a free one, then:
# first, mark the free has_redeemable_jamtrack field if that's still true
# and if still they have more free things, then redeem the giftable_jamtracks
if shopping_cart.free?
if user.has_redeemable_jamtrack
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false)
current_user.has_redeemable_jamtrack = false
else
User.where(id: current_user.id).update_all(gifted_jamtracks: current_user.gifted_jamtracks - 1)
current_user.gifted_jamtracks = current_user.gifted_jamtracks - 1
# create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
jam_track_right.redeemed = shopping_cart.free?
end
end
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid
jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid
jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
unless jam_track_right.save
raise RecurlyClientError.new(jam_track_right.errors)
# also if the purchase was a free one, then:
# first, mark the free has_redeemable_jamtrack field if that's still true
# and if still they have more free things, then redeem the giftable_jamtracks
if shopping_cart.free?
if user.has_redeemable_jamtrack
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false)
current_user.has_redeemable_jamtrack = false
else
User.where(id: current_user.id).update_all(gifted_jamtracks: current_user.gifted_jamtracks - 1)
current_user.gifted_jamtracks = current_user.gifted_jamtracks - 1
end
end
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid
jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid
jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
unless jam_track_right.save
raise RecurlyClientError.new(jam_track_right.errors)
end
end
# blow up the transaction if the JamTrackRight did not get created
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
elsif shopping_cart.is_gift_card?
gift_card_type = cart_product
raise "gift card is null" if gift_card_type.nil?
raise if current_user.nil?
shopping_cart.quantity.times do |item|
gift_card_purchase = GiftCardPurchase.new(
{
user: current_user,
gift_card_type: gift_card_type
})
unless gift_card_purchase.save
raise RecurlyClientError.new(gift_card_purchase.errors)
end
end
else
raise 'unknown shopping cart type: ' + shopping_cart.cart_type
end
# delete the shopping cart; it's been dealt with
shopping_cart.destroy if shopping_cart
# blow up the transaction if the JamTrackRight did not get created
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
end
@ -396,7 +408,7 @@ module JamRuby
def self.create_jam_track_sale(user)
sale = Sale.new
sale.user = user
sale.sale_type = JAMTRACK_SALE
sale.sale_type = JAMTRACK_SALE # gift cards and jam tracks are sold with this type of sale
sale.order_total = 0
sale.save
sale

View File

@ -4,14 +4,16 @@ module JamRuby
JAMBLASTER = 'JamBlaster'
JAMCLOUD = 'JamCloud'
JAMTRACK = 'JamTrack'
GIFTCARD = 'GiftCardType'
belongs_to :sale, class_name: 'JamRuby::Sale'
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
belongs_to :gift_card, class_name: 'JamRuby::GiftCard'
belongs_to :affiliate_referral, class_name: 'JamRuby::AffiliatePartner', foreign_key: :affiliate_referral_id
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK]}
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD]}
validates :unit_price, numericality: {only_integer: false}
validates :quantity, numericality: {only_integer: true}
validates :free, numericality: {only_integer: true}
@ -21,9 +23,19 @@ module JamRuby
validates :recurly_plan_code, presence:true
validates :sale, presence:true
def is_jam_track?
product_type == JAMTRACK
end
def is_gift_card?
product_type == GIFTCARD
end
def product
if product_type == JAMTRACK
JamTrack.find_by_id(product_id)
elsif product_type == GIFTCARD
GiftCardType.find_by_id(product_id)
else
raise 'unsupported product type'
end

View File

@ -28,7 +28,7 @@ module JamRuby
def product_info
product = self.cart_product
{name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region} unless product.nil?
{type: cart_type, name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region, sale_display:product.sale_display} unless product.nil?
end
# multiply quantity by price
@ -90,6 +90,7 @@ module JamRuby
end
def self.create user, product, quantity = 1, mark_redeem = false
cart = ShoppingCart.new
if user.is_a?(User)
cart.user = user
@ -110,40 +111,42 @@ module JamRuby
cart_type == JamTrack::PRODUCT_TYPE
end
def is_gift_card?
cart_type == GiftCardType::PRODUCT_TYPE
end
# returns an array of adjustments for the shopping cart
def create_adjustment_attributes(current_user)
raise "not a jam track" unless is_jam_track?
raise "not a jam track or gift card" unless is_jam_track? || is_gift_card?
info = self.product_info
if free?
puts "GOT A FREEBIE!"
# create the credit, then the pseudo charge
[
{
accounting_code: PURCHASE_FREE_CREDIT,
currency: 'USD',
unit_amount_in_cents: -(info[:total_price] * 100).to_i,
description: "JamTrack: " + info[:name] + " (Credit)",
description: info[:sale_display] + " (Credit)",
tax_exempt: true
},
{
accounting_code: PURCHASE_FREE,
currency: 'USD',
unit_amount_in_cents: (info[:total_price] * 100).to_i,
description: "JamTrack: " + info[:name],
description: info[:sale_display],
tax_exempt: true
}
]
else
[
{
accounting_code: PURCHASE_NORMAL,
currency: 'USD',
unit_amount_in_cents: (info[:total_price] * 100).to_i,
description: "JamTrack: " + info[:name],
description: info[:sale_display],
tax_exempt: false
}
]
@ -152,8 +155,13 @@ module JamRuby
def self.move_to_user(user, anonymous_user, shopping_carts)
shopping_carts.each do |shopping_cart|
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem)
if shopping_cart.is_jam_track?
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem)
else
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, false)
end
end
anonymous_user.destroy_all_shopping_carts
@ -197,7 +205,7 @@ module JamRuby
if clear
# if you are an anonymous user, we make sure there is nothing else in your shopping cart ... keep it clean for the 'new user rummaging around for a freebie scenario'
any_user.destroy_all_shopping_carts
any_user.destroy_jam_track_shopping_carts
any_user.reload
end
@ -207,6 +215,14 @@ module JamRuby
cart
end
def self.add_item_to_cart(any_user, item)
cart = nil
ShoppingCart.transaction do
cart = ShoppingCart.create(any_user, item, 1, false)
end
cart
end
# deletes a jam track from the shopping cart, updating redeem flag as necessary
def self.remove_jam_track_from_cart(any_user, cart)
ShoppingCart.transaction do
@ -236,6 +252,12 @@ module JamRuby
end
end
def self.remove_item_from_cart(any_user, cart)
ShoppingCart.transaction do
cart.destroy
end
end
# if the number of items in the shopping cart is less than gifted_jamtracks on the user, then fix them all up
def self.apply_gifted_jamtracks(user)
jam_track_carts = user.shopping_carts.where(cart_type:JamTrack::PRODUCT_TYPE)

View File

@ -150,6 +150,8 @@ module JamRuby
# gift cards
has_many :gift_cards, :class_name=> "JamRuby::GiftCard"
has_many :gift_card_purchases, :class_name=> "JamRuby::GiftCardPurchase"
# affiliate_partner
has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id, inverse_of: :partner_user
@ -1678,6 +1680,11 @@ module JamRuby
ShoppingCart.where("user_id=?", self).destroy_all
end
def destroy_jam_track_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id, cart_type: JamTrack::PRODUCT_TYPE)
end
def unsubscribe_token
self.class.create_access_token(self)
end

View File

@ -862,7 +862,16 @@ FactoryGirl.define do
factory :gift_card, class: 'JamRuby::GiftCard' do
sequence(:code) {n.to_s}
card_type = JamRuby::GiftCard::JAM_TRACKS_10
card_type JamRuby::GiftCardType::JAM_TRACKS_5
end
factory :gift_card_type, class: 'JamRuby::GiftCardType' do
card_type JamRuby::GiftCardType::JAM_TRACKS_5
end
factory :gift_card_purchase, class: 'JamRuby::GiftCardPurchase' do
association :user, factory: :user
end
end

View File

@ -135,7 +135,7 @@ describe RecurlyTransactionWebHook do
RecurlyTransactionWebHook.create_from_xml(document)
JamTrackRight.find_by_id(jam_track_right.id).should be_nil
JamTrackRight.find_by_id(jam_track_right.id).should_not be_nil
end
it "deletes jam_track_right when voided" do
@ -154,7 +154,7 @@ describe RecurlyTransactionWebHook do
RecurlyTransactionWebHook.create_from_xml(document)
JamTrackRight.find_by_id(jam_track_right.id).should be_nil
JamTrackRight.find_by_id(jam_track_right.id).should_not be_nil
end
end

View File

@ -6,6 +6,7 @@ describe SaleLineItem do
let(:user) {FactoryGirl.create(:user)}
let(:user2) {FactoryGirl.create(:user)}
let(:jam_track) {FactoryGirl.create(:jam_track)}
let(:gift_card) {FactoryGirl.create(:gift_card_type, card_type: GiftCardType::JAM_TRACKS_10)}
describe "associations" do
@ -23,7 +24,7 @@ describe SaleLineItem do
describe "state" do
it "success" do
it "jam track success" do
sale = Sale.create_jam_track_sale(user)
shopping_cart = ShoppingCart.create(user, jam_track)
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
@ -37,5 +38,20 @@ describe SaleLineItem do
success: true
})
end
it "gift card success" do
sale = Sale.create_jam_track_sale(user)
shopping_cart = ShoppingCart.create(user, gift_card)
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
sale_line_item.reload
sale_line_item.state.should eq({
void: false,
refund: false,
fail: false,
success: true
})
end
end
end

View File

@ -7,6 +7,7 @@ describe Sale do
let(:jam_track) {FactoryGirl.create(:jam_track)}
let(:jam_track2) {FactoryGirl.create(:jam_track)}
let(:jam_track3) {FactoryGirl.create(:jam_track)}
let(:gift_card) {GiftCardType.jam_track_5}
def assert_free_line_item(sale_line_item, jamtrack)
sale_line_item.recurly_tax_in_cents.should be_nil
@ -68,6 +69,7 @@ describe Sale do
let(:jamtrack3) { FactoryGirl.create(:jam_track) }
let(:jamtrack4) { FactoryGirl.create(:jam_track) }
let(:jam_track_price_in_cents) { (jamtrack.price * 100).to_i }
let(:gift_card_price_in_cents) { (gift_card.price * 100).to_i }
let(:client) { RecurlyClient.new }
let(:billing_info) {
info = {}
@ -95,6 +97,77 @@ describe Sale do
end
end
it "for a gift card" do
shopping_cart = ShoppingCart.create user, gift_card, 1, false
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart])
user.reload
user.sales.length.should eq(1)
sales.should eq(user.sales)
sale = sales[0]
sale.recurly_invoice_id.should_not be_nil
sale.recurly_subtotal_in_cents.should eq(gift_card_price_in_cents)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(gift_card_price_in_cents)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(gift_card.price)
sale.sale_line_items.length.should == 1
sale_line_item = sale.sale_line_items[0]
# validate we are storing pricing info from recurly
sale_line_item.recurly_tax_in_cents.should eq(0)
sale_line_item.recurly_total_in_cents.should eq(gift_card_price_in_cents)
sale_line_item.recurly_currency.should eq('USD')
sale_line_item.recurly_discount_in_cents.should eq(0)
sale_line_item.product_type.should eq(GiftCardType::PRODUCT_TYPE)
sale_line_item.unit_price.should eq(gift_card.price)
sale_line_item.quantity.should eq(1)
sale_line_item.free.should eq(0)
sale_line_item.sales_tax.should be_nil
sale_line_item.shipping_handling.should eq(0)
sale_line_item.recurly_plan_code.should eq(gift_card.plan_code)
sale_line_item.product_id.should eq(gift_card.id)
sale_line_item.recurly_subscription_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil
# verify subscription is in Recurly
recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments
adjustments.should_not be_nil
adjustments.should have(1).items
purchase= adjustments[0]
purchase.unit_amount_in_cents.should eq((gift_card.price * 100).to_i)
purchase.accounting_code.should eq(ShoppingCart::PURCHASE_NORMAL)
purchase.description.should eq("JamTracks Gift Card (5)")
purchase.state.should eq('invoiced')
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
invoices = recurly_account.invoices
invoices.should have(1).items
invoice = invoices[0]
invoice.uuid.should eq(sale.recurly_invoice_id)
invoice.line_items.should have(1).items # should have single adjustment associated
invoice.line_items[0].should eq(purchase)
invoice.subtotal_in_cents.should eq((gift_card.price * 100).to_i)
invoice.total_in_cents.should eq((gift_card.price * 100).to_i)
invoice.state.should eq('collected')
# verify jam_track_rights data
user.gift_card_purchases.should_not be_nil
user.gift_card_purchases.should have(1).items
user.gift_card_purchases.last.gift_card_type.should eq(GiftCardType.jam_track_5)
user.has_redeemable_jamtrack.should be_true
sale_line_item.affiliate_referral.should be_nil
sale_line_item.affiliate_referral_fee_in_cents.should be_nil
end
it "for a free jam track" do
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
@ -195,48 +268,25 @@ describe Sale do
# OK! Now make a second purchase; this time, buy one free, one not free
shopping_cart3 = ShoppingCart.create user, jamtrack3, 1, true
shopping_cart4 = ShoppingCart.create user, jamtrack4, 1, false
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart3, shopping_cart4])
sales = Sale.place_order(user, [shopping_cart3])
user.reload
user.sales.length.should eq(2)
sale = sales[0]
sale.reload
sale.recurly_invoice_id.should_not be_nil
sale.recurly_invoice_id.should be_nil
sale.recurly_subtotal_in_cents.should eq(0)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(0)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(0)
sale.sale_line_items.length.should == 2
sale.sale_line_items.length.should == 1
assert_free_line_item(sale.sale_line_items[0], jamtrack3)
paid_right = JamTrackRight.where(user_id:user.id).where(jam_track_id: jamtrack4.id).first
sale_line_item.recurly_total_in_cents.should eq(jam_track_price_in_cents)
sale_line_item.recurly_currency.should eq('USD')
sale_line_item.recurly_discount_in_cents.should eq(0)
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
sale_line_item.unit_price.should eq(jamtrack4.price)
sale_line_item.quantity.should eq(1)
sale_line_item.free.should eq(0)
sale_line_item.sales_tax.should be_nil
sale_line_item.shipping_handling.should eq(0)
sale_line_item.recurly_plan_code.should eq(jamtrack4.plan_code)
sale_line_item.product_id.should eq(jamtrack.id)
sale_line_item.recurly_subscription_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should eq(paid_right.recurly_adjustment_uuid)
user.has_redeemable_jamtrack.should be_false
user.gifted_jamtracks.should eq(0)
end
it "for a free jam track with an affiliate association" do

View File

@ -10,6 +10,8 @@ describe ShoppingCart do
let(:jam_track5) { FactoryGirl.create(:jam_track) }
let(:jam_track6) { FactoryGirl.create(:jam_track) }
let(:jam_track7) { FactoryGirl.create(:jam_track) }
let(:gift_card) {FactoryGirl.create(:gift_card_type)}
let(:gift_card2) {FactoryGirl.create(:gift_card_type)}
before(:each) do
ShoppingCart.delete_all
@ -29,7 +31,6 @@ describe ShoppingCart do
user.shopping_carts[0].quantity.should == 1
end
it "maintains only one free JamTrack in ShoppingCart" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart1.should_not be_nil
@ -39,8 +40,12 @@ describe ShoppingCart do
cart2.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
cart3 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true)
cart3.errors.any?.should be_false
cart3 = ShoppingCart.add_item_to_cart(user, gift_card)
cart3.errors.any?.should be_true
user.reload
user.shopping_carts.length.should eq(1)
cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true)
cart4.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
end
@ -56,6 +61,10 @@ describe ShoppingCart do
cart2.errors.any?.should be_true
end
it "a second giftcard just adds quantity" do
end
describe "redeemable behavior" do
it "removes redeemable item to shopping cart (maintains only one in cart)" do
@ -153,6 +162,25 @@ describe ShoppingCart do
end
end
describe "gift cards" do
it "can not add multiple of same type" do
cart1 = ShoppingCart.add_item_to_cart(user, gift_card)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
user.has_redeemable_jamtrack = true
user.shopping_carts.length.should eq(1)
user.shopping_carts[0].quantity.should eql(1)
cart2 = ShoppingCart.add_item_to_cart(user, gift_card)
cart2.should_not be_nil
# it's the same type, so it's blocked
cart2.errors.any?.should be_true
cart2.errors[:cart_id].should eq(["has already been taken"])
end
end
describe "mixed" do
it "non-free then free" do
# you shouldn't be able to add a free after a non-free

View File

@ -95,13 +95,13 @@ end
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] })
DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] })
end
config.around(:each) do |example|
# set no_transaction: true as metadata on your test to use deletion strategy instead
if example.metadata[:no_transaction]
DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }
DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }
else
DatabaseCleaner.strategy = :transaction
end

View File

@ -15,6 +15,7 @@
var $templatePurchasedJamTrack = null;
var $thanksPanel = null;
var $jamTrackInBrowser = null;
var $giftCardPurchased = null;
var $purchasedJamTrack = null;
var $purchasedJamTrackHeader = null;
var $purchasedJamTracks = null;
@ -75,9 +76,17 @@
else {
$thanksPanel.removeClass('hidden')
handleJamTracksPurchased(purchaseResponse.jam_tracks)
handleGiftCardsPurchased(purchaseResponse.gift_cards)
}
}
function handleGiftCardsPurchased(gift_cards) {
// were any GiftCards purchased?
if(gift_cards && gift_cards.length > 0) {
$giftCardPurchased.removeClass('hidden')
}
}
function handleJamTracksPurchased(jamTracks) {
// were any JamTracks purchased?
var jamTracksPurchased = jamTracks && jamTracks.length > 0;
@ -194,6 +203,7 @@
$templatePurchasedJamTrack = $('#template-purchased-jam-track');
$thanksPanel = $screen.find(".thanks-panel");
$jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser");
$giftCardPurchased = $screen.find('.thanks-detail.gift-card')
$purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
$purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
$purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")

View File

@ -135,15 +135,7 @@
}
}
function displayTax(effectiveQuantity, item_tax, total_with_tax) {
var totalTax = 0;
var totalPrice = 0;
var unitTax = item_tax * effectiveQuantity;
totalTax += unitTax;
var totalUnitPrice = total_with_tax * effectiveQuantity;
totalPrice += totalUnitPrice;
function displayTax(totalTax, totalPrice) {
$screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2))
$screen.find('.order-right-page .order-items-value.grand-total').text('$' + totalPrice.toFixed(2))
@ -181,8 +173,16 @@
taxRate = 0.0825;
}
var unitTax = 1.99 * taxRate;
displayTax(effectiveQuantity, unitTax, 1.99 + unitTax)
var estimatedTax = 0;
var estimatedTotal = 0;
context._.each(carts, function(cart) {
var cart_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
estimatedTax += cart.product_info.price * cart_quantity * taxRate;
estimatedTotal += cart.product_info.price * cart_quantity;
})
displayTax(Math.round(estimatedTax*100)/100, Math.round((estimatedTotal + estimatedTax)*100)/100)
}
else {
checkoutUtils.configureRecurly()

View File

@ -1805,6 +1805,17 @@
return deferred
}
function addGiftCardToShoppingCart(options) {
var deferred = $.ajax({
type: "POST",
url: '/api/shopping_carts/add_gift_card?' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
return deferred
}
function getShoppingCarts() {
// the need for the time de-duplicator indicates we are doing something wrong on the server
return $.ajax({
@ -2188,6 +2199,7 @@
this.enqueueJamTrack = enqueueJamTrack;
this.getBackingTracks = getBackingTracks;
this.addJamtrackToShoppingCart = addJamtrackToShoppingCart;
this.addGiftCardToShoppingCart = addGiftCardToShoppingCart;
this.getShoppingCarts = getShoppingCarts;
this.removeShoppingCart = removeShoppingCart;
this.clearShoppingCart = clearShoppingCart;

View File

@ -0,0 +1,93 @@
context = window
rest = context.JK.Rest()
@GiftCardLandingPage = React.createClass({
render: () ->
if this.state.done
ctaButtonText10 = 'sending you in...'
ctaButtonText20 = 'sending you in...'
else if this.state.processing
ctaButtonText10 = 'hold on...'
ctaButtonText20 = 'hold on...'
else
ctaButtonText10 = `<span>ADD $10 CARD<br/>TO CART</span>`
ctaButtonText20 = `<span>ADD $20 CARD<br/>TO CART</span>`
ctaButtons =
`<div className="cta-buttons">
<button className={classNames({'five-jt': true, 'gift-card': true, 'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick.bind(this, 5)}>{ctaButtonText10}</button>
<button className={classNames({'ten-jt': true, 'gift-card': true, 'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick.bind(this, 10)}>{ctaButtonText20}</button>
</div>`
`<div className="top-container">
<div className="full-row name-and-artist">
<div>
<img className="gift-card-preview" width="300" height="191" src="/assets/landing/gift_card.png" alt="gift card "/>
<h1 className="jam-track-name">$10 or $20 JAMTRACKS GIFT CARDS</h1>
<h2 className="original-artist">A PERFECT GIFT FOR THE HOLIDAYS</h2>
<div className="clearall"/>
</div>
<div className="preview-and-action-box">
<img src="/assets/landing/jamtrack_landing_arrow_1.png" className="arrow1" />
<img src="/assets/landing/jamtrack_landing_arrow_2.png" className="arrow2" />
<div className="preview-jamtrack-header">
Preview JamTrack
</div>
<div className={classNames({'preview-area': true, 'gift-card': true})}>
<p>Click the play buttons below to preview the master mix and 20-second samples of all the isolated tracks.</p>
<div className="tracks previews">
</div>
<p className="gift-getter">
Get a $10 gift card (good for 5 songs) or a $20 gift card (good for 10 songs), and your happy
gift card getter can choose their favorites from our catalog of 3,700+ popular songs.
</p>
{ctaButtons}
<a className="browse-all" href="/client?search=#/jamtrack/search">or browse our catalog of 3,700+ songs</a>
</div>
</div>
</div>
<div className="row summary-text">
<p className="top-summary">
JamTracks by JamKazam are the best way to play along with your favorite songs. Far better and different than traditional
backing tracks, our JamTracks are complete multi-track professional recordings, with fully isolated tracks for each part of the music.
And our free app and Internet service are packed with features that give you unmatched creative freedom to learn, practice, record, play with others, and share your performances.
</p>
</div>
</div>`
getInitialState: () ->
{processing:false}
componentDidMount:() ->
$root = $(this.getDOMNode())
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
ctaClick: (card_type, e) ->
e.preventDefault()
return if @state.processing
loggedIn = context.JK.currentUserId?
rest.addGiftCardToShoppingCart({id: card_type}).done((response) =>
if loggedIn
@setState({done: true})
context.location = '/client#/shoppingCart'
else
@setState({done: true})
context.location = '/client#/checkoutPayment'
).fail((jqXHR, textStatus, errorMessage) =>
if jqXHR.status == 422
errors = JSON.parse(jqXHR.responseText)
cart_errors = errors?.errors?.cart_id
context.JK.app.ajaxError(jqXHR, textStatus, errorMessage)
@setState({processing:false})
)
})

View File

@ -24,8 +24,8 @@ logger = context.JK.logger
onModify: (changes) ->
@user = $.extend({}, @user, changes)
@changed(
)
@changed()
changed:() ->
@trigger({user: @user})
}

View File

@ -73,6 +73,10 @@
}
}
}
.thanks-detail.gift-card{
margin-top: 20px;
}
.thanks-detail.purchased-jam-track {
margin-top:20px;

View File

@ -162,6 +162,18 @@ body.web.individual_jamtrack {
text-align: center;
}
img.gift-card-preview {
width:300px;
float: left;
margin-left: -15px; // because image has black on the left, which you can't see on back background
margin-right: 20px;
margin-bottom: 20px;
}
p.gift-getter {
margin-top:20px;
line-height:125%;
}
img.app-preview {
width: 340px;
float: left;
@ -205,6 +217,9 @@ body.web.individual_jamtrack {
.browse-all {
color: #ffb800;
text-decoration: underline;
text-align: center;
display: block;
}
p {
@ -320,6 +335,11 @@ body.web.individual_jamtrack {
padding: 10px;
border-width: 0 0 $chunkyBorderWidth;
&.gift-card {
border-width: 0 0 2px;
}
border-style: solid;
border-color: $copy-color-on-dark;
@ -338,6 +358,9 @@ body.web.individual_jamtrack {
margin-bottom:10px;
}
.cta-buttons {
text-align:center;
}
.cta-button {
font-size: 24px;
color: white;
@ -348,6 +371,13 @@ body.web.individual_jamtrack {
width: 100%;
border: 1px outset buttonface;
font-family: Raleway, Arial, Helvetica, sans-serif;
&.gift-card {
font-size:16px;
width:138px;
margin:15px 5px;
display:inline-block;
}
}
}
@ -385,12 +415,7 @@ body.web.individual_jamtrack {
border: 1px outset buttonface;
font-family: Raleway, Arial, Helvetica, sans-serif;
}
.browse-all {
text-decoration: underline;
text-align: center;
display: block;
}
.privacy-policy {
text-decoration: underline;
}

View File

@ -124,7 +124,7 @@ class ApiRecurlyController < ApiController
def place_order
error=nil
response = {jam_tracks: []}
response = {jam_tracks: [], gift_cards: []}
if Sale.is_mixed(current_user.shopping_carts)
msg = "has free and non-free items. Try removing non-free items."
@ -136,11 +136,16 @@ class ApiRecurlyController < ApiController
sales.each do |sale|
if sale.is_jam_track_sale?
sale.sale_line_items.each do |line_item|
sale.sale_line_items.each do |line_item|
if line_item.is_jam_track?
jam_track = line_item.product
jam_track_right = jam_track.right_for_user(current_user)
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
elsif line_item.is_gift_card?
gift_card = line_item.product
response[:gift_cards] << {name: gift_card.name, id: gift_card.id}
else
raise 'unknown sale line item type: ' + line_item.product_type
end
end
end

View File

@ -30,6 +30,34 @@ class ApiShoppingCartsController < ApiController
end
end
def add_gift_card
gift_card_type = nil
id = params[:id]
if id && id.to_i == 5
gift_card_type = 'jam_tracks_5'
elsif id && id.to_i == 10
gift_card_type = 'jam_tracks_10'
end
gift_card = GiftCardType.find_by_id(gift_card_type)
# verify GiftCard exists
if gift_card.nil?
raise StateError, "Invalid JamTrack."
end
@cart = ShoppingCart.add_item_to_cart(any_user, gift_card)
if @cart.errors.any?
response.status = :unprocessable_entity
respond_with @cart
else
# let add_gift_card.rabl take over
end
end
def update_cart
@cart = ShoppingCart.find_by_id params[:id]

View File

@ -213,5 +213,28 @@ class LandingsController < ApplicationController
@landing_tag_play_learn_earn = true
render 'redeem_giftcard', layout: 'web'
end
def buy_gift_card
@no_landing_tag = true
@landing_tag_play_learn_earn = true
@show_after_black_bar_border = true
@jam_track = JamTrack.find_by_slug('elton-john-rocket-man')
@jam_track = JamTrack.first unless @jam_track
instrument_id = nil
instrument_name = nil
instrument_count = 0
band_jam_track_count = @jam_track.band_jam_track_count
jam_track_count = JamTrack.count
@title = individual_jamtrack_title(false, params[:generic], @jam_track)
@description = individual_jamtrack_desc(false, params[:generic], @jam_track)
@page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: false, generic: params[:generic], instrument: instrument_name, instrument_id: instrument_id, instrument_count: instrument_count}
gon.jam_track_plan_code = @jam_track.plan_code if @jam_track
gon.generic = params[:generic]
gon.instrument_id = instrument_id
render 'buy_gift_card', layout: 'web'
end
end

View File

@ -0,0 +1,5 @@
extends "api_shopping_carts/show"
node :show_free_jamtrack do
any_user.show_free_jamtrack?
end

View File

@ -19,6 +19,8 @@ div layout="screen" layout-id="checkoutComplete" id="checkoutCompleteScreen" cla
br
.thanks-detail We'll send you an email confirming your order shortly.
br
.thanks-detail.gift-card.hidden
p Thank you for purchasing a JamTrack Gift Card! It will be mailed to you.
.thanks-detail.jam-tracks-in-browser.hidden
p To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session.
a.download-jamkazam-wrapper.hidden href="/downloads" rel="external"

View File

@ -90,7 +90,7 @@ script type='text/template' id='template-order-content'
= "{% _.each(data.carts, function(cart) { %}"
.cart-item cart-id="{{cart.id}}"
.cart-item-caption
= "{{cart.cart_type}}: {{cart.product_info.name}}"
= "{{cart.product_info.sale_display}}"
= "{% if (cart.product_info.free) { %}"
span.first-one-free
| (first one free)

View File

@ -10,8 +10,7 @@ div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class
.checkout-navigation-bar
.payment-wrapper
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.&nbsp;
| But we need this data to prevent fraud/abuse of those who would create multiple accounts to collect multiple free JamTracks.&nbsp;
| Please enter your billing address and payment information below.&nbsp;
| You will not be billed for any charges of any kind without your explicit authorization.&nbsp;
| There are no "hidden" charges or fees, thank you!
p.payment-prompt.no-free-jamtrack.hidden

View File

@ -40,7 +40,7 @@
= "{% _.each(data.carts, function(cart, index) { %}"
%tr.cart-item{"cart-id" => "{{cart.id}}"}
%td.cart-item-caption
{{cart.cart_type}}: {{cart.product_info.name}}
{{cart.product_info.sale_display}}
%td.cart-item-price
$ {{Number(cart.product_info.real_price).toFixed(2)}}
= "{% if(index == data.carts.length - 1) { %}"

View File

@ -0,0 +1,24 @@
- provide(:page_name, 'landing_page full individual_jamtrack')
- provide(:description, @description)
- provide(:title, @title)
= react_component 'GiftCardLandingPage', @page_data.to_json
- content_for :after_black_bar do
.row.cta-row
h2 GET YOUR GIFT CARD NOW!
p And join 20,000+ other musicians who love our JamTracks.
p.cta-text Not sure if JamTracks are for you? Scroll down to learn more.
- content_for :white_bar do
= react_component 'JamTrackLandingBottomPage', @page_data.to_json
- content_for :red_bar do
.full-row
| Get your free JamTrack and start playing today!
javascript:
$(document).on('JAMKAZAM_READY', function(e, data) {
var song = new JK.IndividualJamTrack(data.app, true);
song.initialize();
})

View File

@ -41,6 +41,7 @@ SampleApp::Application.routes.draw do
match '/landing/jamtracks/:plan_code', to: 'landings#individual_jamtrack', via: :get, as: 'individual_jamtrack'
match '/landing/jamtracks/band/:plan_code', to: 'landings#individual_jamtrack_band', via: :get, as: 'individual_jamtrack_band'
match '/landing/jamtracks/:instrument/:plan_code', to: 'landings#individual_jamtrack', via: :get, as: 'individual_jamtrack_instrument'
match '/landing/gift-card', to: 'landings#buy_gift_card', via: :get, as: 'buy_gift_card'
match '/affiliateProgram', to: 'landings#affiliate_program', via: :get, as: 'affiliate_program'
@ -276,6 +277,7 @@ SampleApp::Application.routes.draw do
match '/shopping_carts' => 'api_shopping_carts#index', :via => :get
match '/shopping_carts' => 'api_shopping_carts#remove_cart', :via => :delete
match '/shopping_carts/clear_all' => 'api_shopping_carts#clear_all', :via => :delete
match '/shopping_carts/add_gift_card' => 'api_shopping_carts#add_gift_card', :via => :post
# RSVP requests
match '/rsvp_requests' => 'api_rsvp_requests#index', :via => :get

View File

@ -24,7 +24,7 @@ describe ApiUsersController do
gift_card.reload
user.gift_cards.should eq([gift_card])
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
gift_card.user.should eq(user)
end
@ -42,7 +42,7 @@ describe ApiUsersController do
gift_card.reload
user.gift_cards.should eq([gift_card])
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
gift_card.user.should eq(user)
end
@ -78,7 +78,7 @@ describe ApiUsersController do
gift_card.reload
user.gift_cards.should eq([gift_card])
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
gift_card.user.should eq(user)
cart1.reload
cart1.marked_for_redeem.should eq(1)
@ -100,7 +100,7 @@ describe ApiUsersController do
gift_card.reload
user.gift_cards.should eq([gift_card])
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
gift_card.user.should eq(user)
cart1.reload
cart1.marked_for_redeem.should eq(1)
@ -125,7 +125,7 @@ describe ApiUsersController do
gift_card.reload
user.gift_cards.should eq([gift_card])
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
gift_card.user.should eq(user)
user.shopping_carts.each do |cart|
cart.marked_for_redeem.should eq(0)

View File

@ -842,6 +842,6 @@ FactoryGirl.define do
factory :gift_card, class: 'JamRuby::GiftCard' do
sequence(:code) {|n| n.to_s}
card_type GiftCard::JAM_TRACKS_10
card_type GiftCard::JAM_TRACKS_5
end
end

View File

@ -827,8 +827,6 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
acdc_sale.free.should eq(0)
acdc_sale.unit_price.should eq(1.99)
acdc_sale.sale.should eq(sale)
end
it "for anonymous user with referral" do

View File

@ -0,0 +1,245 @@
require 'spec_helper'
describe "Gift Card Landing", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
ShoppingCart.delete_all
JamTrackRight.delete_all
JamTrack.delete_all
JamTrackTrack.delete_all
JamTrackLicensor.delete_all
GiftCardPurchase.delete_all
GiftCard.delete_all
end
before(:all) do
@jamtrack_rocketman = FactoryGirl.create(:jam_track, slug: 'elton-john-rocket-man', name: 'Rocket Man', original_artist: 'Elton John', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack')
end
let(:jamtrack_rocketman) {@jamtrack_rocketman}
let(:user) { FactoryGirl.create(:user, country: 'US') }
let(:billing_info) {
{
first_name: 'Seth',
last_name: 'Call',
address1: '10704 Buckthorn Drive',
city: 'Austin',
state: 'Texas',
country: 'US',
zip: '78759',
number: '4111111111111111',
month: '08',
year: '2017',
verification_value: '012'
}
}
it "logged out (5) and affiliate" do
partner = FactoryGirl.create(:affiliate_partner)
affiliate_params = partner.affiliate_query_params
visit "/landing/gift-card?" + affiliate_params
find('h1.jam-track-name', '$10 or $20 JAMTRACKS GIFT CARDS')
find('h2.original-artist', 'A PERFECT GIFT FOR THE HOLIDAYS')
jamtrack_rocketman.jam_track_tracks.each do |track|
if track.master?
find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]')
find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: 'Master Mix')
else
find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]')
find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: track.instrument.description)
end
end
find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search")
find('button.five-jt', text: 'ADD $10 CARD TO CART').trigger(:click)
find('h1', text: 'check out')
# fill out all billing info and account info
fill_in 'billing-first-name', with: 'Seth'
fill_in 'billing-last-name', with: 'Call'
fill_in 'billing-address1', with: '10704 Buckthorn Drive'
fill_in 'billing-city', with: 'Austin'
fill_in 'billing-state', with: 'Texas'
fill_in 'billing-zip', with: '78759'
fill_in 'card-number', with: '4111111111111111'
fill_in 'card-verify', with: '012'
# fill in user/email/tos
fill_in 'email', with: 'bogus+gc1@jamkazam.com'
fill_in 'password', with: 'jam123'
find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS
# try to submit, and see order page
find('#payment-info-next').trigger(:click)
find('.order-items-value.sub-total', text:'10.00')
find('.order-items-value.taxes', text:'0.83')
find('.order-items-value.order-total', text:'$10.00')
find('.order-items-value.grand-total', text:'$10.83')
# click the ORDER button
find('.place-order-center a.button-orange.place-order').trigger(:click)
# and now we should see confirmation, and a notice that we are in a normal browser
find('.thanks-detail.gift-card')
created_user = User.find_by_email('bogus+gc1@jamkazam.com')
sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it
created_user.reload
created_user.has_redeemable_jamtrack.should be_true
created_user.gifted_jamtracks.should eq(0)
created_user.gift_card_purchases.length.should eq(1)
# verify sales data
created_user.sales.length.should eq(1)
sale = created_user.sales.last
sale.sale_line_items.length.should eq(1)
line_item = sale.sale_line_items[0]
line_item.product_type.should eq('GiftCardType')
line_item.product_id.should eq('jam_tracks_5')
line_item.quantity.should eq(1)
line_item.free.should eq(0)
line_item.unit_price.should eq(10.00)
line_item.sale.should eq(sale)
line_item.affiliate_referral.should eq(partner)
line_item.affiliate_refunded.should be_false
line_item.affiliate_refunded_at.should be_nil
line_item.affiliate_referral_fee_in_cents.should eq(10.00 * partner.rate * 100)
end
it "logged out (10)" do
visit "/landing/gift-card"
find('h1.jam-track-name', '$10 or $20 JAMTRACKS GIFT CARDS')
find('h2.original-artist', 'A PERFECT GIFT FOR THE HOLIDAYS')
jamtrack_rocketman.jam_track_tracks.each do |track|
if track.master?
find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]')
find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: 'Master Mix')
else
find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]')
find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: track.instrument.description)
end
end
find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search")
find('button.ten-jt', text: 'ADD $20 CARD TO CART').trigger(:click)
find('h1', text: 'check out')
# fill out all billing info and account info
fill_in 'billing-first-name', with: 'Seth'
fill_in 'billing-last-name', with: 'Call'
fill_in 'billing-address1', with: '10704 Buckthorn Drive'
fill_in 'billing-city', with: 'Austin'
fill_in 'billing-state', with: 'Texas'
fill_in 'billing-zip', with: '78759'
fill_in 'card-number', with: '4111111111111111'
fill_in 'card-verify', with: '012'
# fill in user/email/tos
fill_in 'email', with: 'bogus+gc2@jamkazam.com'
fill_in 'password', with: 'jam123'
find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS
# try to submit, and see order page
find('#payment-info-next').trigger(:click)
find('.order-items-value.sub-total', text:'20.00')
find('.order-items-value.taxes', text:'1.65')
find('.order-items-value.order-total', text:'$20.00')
find('.order-items-value.grand-total', text:'$21.65')
# click the ORDER button
find('.place-order-center a.button-orange.place-order').trigger(:click)
# and now we should see confirmation, and a notice that we are in a normal browser
find('.thanks-detail.gift-card')
created_user = User.find_by_email('bogus+gc2@jamkazam.com')
sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it
created_user.reload
created_user.has_redeemable_jamtrack.should be_true
created_user.gifted_jamtracks.should eq(0)
created_user.gift_card_purchases.length.should eq(1)
# verify sales data
created_user.sales.length.should eq(1)
sale = created_user.sales.last
sale.sale_line_items.length.should eq(1)
line_item = sale.sale_line_items[0]
line_item.product_type.should eq('GiftCardType')
line_item.product_id.should eq('jam_tracks_10')
line_item.quantity.should eq(1)
line_item.free.should eq(0)
line_item.unit_price.should eq(20.00)
line_item.sale.should eq(sale)
end
it "logged in (5)" do
fast_signin(user,"/landing/gift-card")
find('h1.jam-track-name', '$10 or $20 JAMTRACKS GIFT CARDS')
find('h2.original-artist', 'A PERFECT GIFT FOR THE HOLIDAYS')
jamtrack_rocketman.jam_track_tracks.each do |track|
if track.master?
find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]')
find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: 'Master Mix')
else
find('.tracks.previews[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]')
find('.tracks.previews[data-id="' + track.id + '"] .instrument-name', text: track.instrument.description)
end
end
find('a.browse-all')['href'].should eq("/client?search=#/jamtrack/search")
find('button.five-jt', text: 'ADD $10 CARD TO CART').trigger(:click)
# land in shopping cart because we are a user; log in
find('.proceed-checkout').trigger(:click)
find('h1', text: 'check out')
# fill out all billing info and account info
fill_in 'billing-first-name', with: 'Seth'
fill_in 'billing-last-name', with: 'Call'
fill_in 'billing-address1', with: '10704 Buckthorn Drive'
fill_in 'billing-city', with: 'Austin'
fill_in 'billing-state', with: 'Texas'
fill_in 'billing-zip', with: '78759'
fill_in 'card-number', with: '4111111111111111'
fill_in 'card-verify', with: '012'
# try to submit, and see order page
find('#payment-info-next').trigger(:click)
find('.order-items-value.sub-total', text:'10.00')
find('.order-items-value.taxes', text:'0.83')
find('.order-items-value.order-total', text:'$10.00')
find('.order-items-value.grand-total', text:'$10.83')
# click the ORDER button
find('.place-order-center a.button-orange.place-order').trigger(:click)
# and now we should see confirmation, and a notice that we are in a normal browser
find('.thanks-detail.gift-card')
user.reload
sleep 3 # challenge to all comers! WHY DO I HAVE TO SLEEP FOR THIS ASSERTION TO BE TRUE! GAH . and 1 second won't do it
user.reload
user.has_redeemable_jamtrack.should be_true
user.gifted_jamtracks.should eq(0)
user.gift_card_purchases.length.should eq(1)
end
end

View File

@ -30,13 +30,13 @@ describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature =
find('.done-action a.go-browse').trigger(:click)
find('.no-free-jamtrack')
find('.free-jamtrack')
user = User.find_by_email("gifter1@jamkazam.com")
gift_card.reload
gift_card.user.should eq(user)
user.reload
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
end
it "validates correctly" do
@ -58,13 +58,13 @@ describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature =
find('.done-action a.go-browse').trigger(:click)
find('.no-free-jamtrack')
find('.free-jamtrack')
user = User.find_by_email("gifter2@jamkazam.com")
gift_card.reload
gift_card.user.should eq(user)
user.reload
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
end
it "converts shopping cart items to free" do
@ -89,7 +89,7 @@ describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature =
find('.done-action a.go-browse').trigger(:click)
find('.no-free-jamtrack')
find('.free-jamtrack')
cart.reload
cart.marked_for_redeem.should eq(1)
@ -108,12 +108,12 @@ describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature =
find('.done-action a.go-browse').trigger(:click)
find('.no-free-jamtrack')
find('.free-jamtrack')
gift_card.reload
gift_card.user.should eq(user1)
user1.reload
user1.gifted_jamtracks.should eq(10)
user1.gifted_jamtracks.should eq(5)
end
end
@ -133,12 +133,12 @@ describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature =
find('.done-action a.go-browse').trigger(:click)
find('.no-free-jamtrack')
find('.free-jamtrack')
gift_card.reload
gift_card.user.should eq(user1)
user1.reload
user1.gifted_jamtracks.should eq(10)
user1.gifted_jamtracks.should eq(5)
end
it "converts shopping cart items to free" do
@ -156,12 +156,12 @@ describe "Redeem Gift Card", :js => true, :type => :feature, :capybara_feature =
fill_in "code", with: gift_card.code
find('button.redeem-giftcard').trigger(:click)
find('.done-action a.go-browse').trigger(:click)
find('.no-free-jamtrack')
find('.free-jamtrack')
gift_card.reload
gift_card.user.should eq(user1)
user1.reload
user1.gifted_jamtracks.should eq(10)
user1.gifted_jamtracks.should eq(5)
cart.reload
cart.marked_for_redeem.should eq(1)
end

View File

@ -730,7 +730,7 @@ describe UserManager do
gift_card.user.should eq(user)
user = User.find(user.id)
user.has_redeemable_jamtrack.should be_true
user.gifted_jamtracks.should eq(10)
user.gifted_jamtracks.should eq(5)
user.gift_cards[0].should eq(gift_card)
end