diff --git a/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb b/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb
index 1d3232169..d94dca2e0 100644
--- a/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb
+++ b/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb
@@ -2,6 +2,8 @@ module JamRuby
class RecurlyTransactionWebHook < ActiveRecord::Base
belongs_to :user, class_name: 'JamRuby::User'
+ belongs_to :sale_line_item, class_name: 'JamRuby::SaleLineItem', foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid', inverse_of: :recurly_transactions
+ belongs_to :sale, class_name: 'JamRuby::Sale', foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id', inverse_of: :recurly_transactions
validates :recurly_transaction_id, presence: true
validates :action, presence: true
@@ -9,11 +11,24 @@ module JamRuby
validates :amount_in_cents, numericality: {only_integer: true}
validates :user, presence: true
+
SUCCESSFUL_PAYMENT = 'payment'
FAILED_PAYMENT = 'failed_payment'
REFUND = 'refund'
VOID = 'void'
+ def is_credit_type?
+ transaction_type == REFUND || transaction_type == VOID
+ end
+
+ def is_voided?
+ transaction_type == VOID
+ end
+
+ def is_refund?
+ transaction_type == REFUND
+ end
+
def self.is_transaction_web_hook?(document)
return false if document.root.nil?
diff --git a/ruby/lib/jam_ruby/models/sale.rb b/ruby/lib/jam_ruby/models/sale.rb
index 06addb8e3..cb2aba3b0 100644
--- a/ruby/lib/jam_ruby/models/sale.rb
+++ b/ruby/lib/jam_ruby/models/sale.rb
@@ -8,9 +8,66 @@ module JamRuby
belongs_to :user, class_name: 'JamRuby::User'
has_many :sale_line_items, class_name: 'JamRuby::SaleLineItem'
+ has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale, foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id'
+
validates :order_total, numericality: {only_integer: false}
validates :user, presence: true
+ @@log = Logging.logger[Sale]
+
+ def self.index(user, params = {})
+
+ limit = params[:per_page]
+ limit ||= 20
+ limit = limit.to_i
+
+ query = Sale.limit(limit)
+ .includes([:recurly_transactions, :sale_line_items])
+ .where('sales.user_id' => user.id)
+ .order('sales.created_at DESC')
+
+ current_page = params[:page].nil? ? 1 : params[:page].to_i
+ next_page = current_page + 1
+
+ # will_paginate gem
+ query = query.paginate(:page => current_page, :per_page => limit)
+
+ if query.length == 0 # no more results
+ { query: query, next_page: nil}
+ elsif query.length < limit # no more results
+ { query: query, next_page: nil}
+ else
+ { query: query, next_page: next_page }
+ end
+
+ end
+
+ def state
+ original_total = self.recurly_total_in_cents
+
+ is_voided = false
+ refund_total = 0
+
+ recurly_transactions.each do |transaction|
+ if transaction.is_voided?
+ is_voided = true
+ else
+
+ end
+
+ if transaction.is_refund?
+ refund_total = refund_total + transaction.amount_in_cents
+ end
+ end
+
+ # if refund_total is > 0, then you have a refund.
+ # if voided is true, then in theory the whole thing has been refunded
+ {
+ voided: is_voided,
+ original_total: original_total,
+ refund_total: refund_total
+ }
+ end
def self.preview_invoice(current_user, shopping_carts)
@@ -102,10 +159,6 @@ module JamRuby
sale.recurly_total_in_cents = invoice.total_in_cents
sale.recurly_currency = invoice.currency
- puts "Sale Line Items #{sale.sale_line_items.inspect}"
-
- puts "----"
- puts "Invoice Line Items #{invoice.line_items.inspect}"
# and resolve against sale_line_items
sale.sale_line_items.each do |sale_line_item|
found_line_item = false
@@ -120,7 +173,7 @@ module JamRuby
end
if !found_line_item
- @@loge.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
+ @@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
puts "CANT FIND LINE ITEM"
end
diff --git a/ruby/lib/jam_ruby/models/sale_line_item.rb b/ruby/lib/jam_ruby/models/sale_line_item.rb
index 0685244f2..2022318b0 100644
--- a/ruby/lib/jam_ruby/models/sale_line_item.rb
+++ b/ruby/lib/jam_ruby/models/sale_line_item.rb
@@ -1,14 +1,15 @@
module JamRuby
class SaleLineItem < ActiveRecord::Base
- belongs_to :sale, class_name: 'JamRuby::Sale'
- belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
- belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
-
JAMBLASTER = 'JamBlaster'
JAMCLOUD = 'JamCloud'
JAMTRACK = 'JamTrack'
+ belongs_to :sale, class_name: 'JamRuby::Sale'
+ belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
+ belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
+ has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
+
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK]}
validates :unit_price, numericality: {only_integer: false}
validates :quantity, numericality: {only_integer: true}
@@ -19,10 +20,45 @@ module JamRuby
validates :sale, presence:true
def product
- # TODO: beef up if there is more than one sort of sale
- JamTrack.find(product_id)
+ if product_type == JAMTRACK
+ JamTrack.find_by_id(product_id)
+ else
+ raise 'unsupported product type'
+ end
end
+ def product_info
+ item = product
+ { name: product.name } if item
+ end
+
+ def state
+ voided = false
+ refunded = false
+ failed = false
+ succeeded = false
+
+ recurly_transactions.each do |transaction|
+ if transaction.transaction_type == RecurlyTransactionWebHook::VOID
+ voided = true
+ elsif transaction.transaction_type == RecurlyTransactionWebHook::REFUND
+ refunded = true
+ elsif transaction.transaction_type == RecurlyTransactionWebHook::FAILED_PAYMENT
+ failed = true
+ elsif transaction.transaction_type == RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT
+ succeeded = true
+ end
+ end
+
+ {
+ void: voided,
+ refund: refunded,
+ fail: failed,
+ success: succeeded
+ }
+ end
+
+
def self.create_from_shopping_cart(sale, shopping_cart, recurly_subscription_uuid, recurly_adjustment_uuid, recurly_adjustment_credit_uuid)
product_info = shopping_cart.product_info
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 4d9b3b21a..5a94e03a3 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -375,6 +375,10 @@ module JamRuby
self.purchased_jam_tracks.count
end
+ def sales_count
+ self.sales.count
+ end
+
def joined_score
return nil unless has_attribute?(:score)
a = read_attribute(:score)
diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb
index 1ff419667..ec88cdaa5 100644
--- a/ruby/lib/jam_ruby/recurly_client.rb
+++ b/ruby/lib/jam_ruby/recurly_client.rb
@@ -61,12 +61,20 @@ module JamRuby
account
end
- def payment_history(current_user)
+ def payment_history(current_user, options ={})
+
+ limit = params[:limit]
+ limit ||= 20
+ limit = limit.to_i
+
+ cursor = options[:cursor]
+
payments = []
account = get_account(current_user)
if(account.present?)
begin
- account.transactions.find_each do |transaction|
+
+ account.transaction.paginate(per_page:limit, cursor:cursor).each do |transaction|
# XXX this isn't correct because we create 0 dollar transactions too (for free stuff)
#if transaction.amount_in_cents > 0 # Account creation adds a transaction record
payments << {
@@ -74,7 +82,8 @@ module JamRuby
:amount_in_cents => transaction.amount_in_cents,
:status => transaction.status,
:payment_method => transaction.payment_method,
- :reference => transaction.reference
+ :reference => transaction.reference,
+ :plan_code => transaction.plan_code
}
#end
end
diff --git a/ruby/spec/jam_ruby/models/sale_line_item_spec.rb b/ruby/spec/jam_ruby/models/sale_line_item_spec.rb
new file mode 100644
index 000000000..334166734
--- /dev/null
+++ b/ruby/spec/jam_ruby/models/sale_line_item_spec.rb
@@ -0,0 +1,41 @@
+
+require 'spec_helper'
+
+describe SaleLineItem do
+
+ let(:user) {FactoryGirl.create(:user)}
+ let(:user2) {FactoryGirl.create(:user)}
+ let(:jam_track) {FactoryGirl.create(:jam_track)}
+
+ describe "associations" do
+
+ it "can find associated recurly transaction web hook" do
+ sale = Sale.create_jam_track_sale(user)
+ shopping_cart = ShoppingCart.create(user, jam_track)
+ sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
+ transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
+
+ sale_line_item.reload
+ sale_line_item.recurly_transactions.should eq([transaction])
+ end
+ end
+
+
+ describe "state" do
+
+ it "success" do
+ sale = Sale.create_jam_track_sale(user)
+ shopping_cart = ShoppingCart.create(user, jam_track)
+ sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
+ transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
+
+ sale_line_item.reload
+ sale_line_item.state.should eq({
+ void: false,
+ refund: false,
+ fail: false,
+ success: true
+ })
+ end
+ end
+end
diff --git a/ruby/spec/jam_ruby/models/sale_spec.rb b/ruby/spec/jam_ruby/models/sale_spec.rb
index 91d298b32..b08aad36d 100644
--- a/ruby/spec/jam_ruby/models/sale_spec.rb
+++ b/ruby/spec/jam_ruby/models/sale_spec.rb
@@ -2,6 +2,46 @@ require 'spec_helper'
describe Sale do
+ let(:user) {FactoryGirl.create(:user)}
+ let(:user2) {FactoryGirl.create(:user)}
+ let(:jam_track) {FactoryGirl.create(:jam_track)}
+
+ describe "index" do
+ it "empty" do
+ result = Sale.index(user)
+ result[:query].length.should eq(0)
+ result[:next].should eq(nil)
+ end
+
+ it "one" do
+ sale = Sale.create_jam_track_sale(user)
+ shopping_cart = ShoppingCart.create(user, jam_track)
+ sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
+
+ result = Sale.index(user)
+ result[:query].length.should eq(1)
+ result[:next].should eq(nil)
+ end
+
+ it "user filtered correctly" do
+ sale = Sale.create_jam_track_sale(user)
+ shopping_cart = ShoppingCart.create(user, jam_track)
+ sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
+
+ result = Sale.index(user)
+ result[:query].length.should eq(1)
+ result[:next].should eq(nil)
+
+ sale2 = Sale.create_jam_track_sale(user2)
+ shopping_cart = ShoppingCart.create(user2, jam_track)
+ sale_line_item2 = SaleLineItem.create_from_shopping_cart(sale2, shopping_cart, nil, 'some_adjustment_uuid', nil)
+
+ result = Sale.index(user)
+ result[:query].length.should eq(1)
+ result[:next].should eq(nil)
+ end
+ end
+
describe "place_order" do
diff --git a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb
index 9daf3d449..13f6777bc 100644
--- a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb
+++ b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb
@@ -24,9 +24,10 @@ describe ShoppingCart do
it "should not add duplicate JamTrack to ShoppingCart" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
+ cart1.errors.any?.should be_false
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
- cart2.should be_nil
+ cart2.errors.any?.should be_true
end
diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js
index 5df29a6bd..b7af8f033 100644
--- a/web/app/assets/javascripts/accounts.js
+++ b/web/app/assets/javascripts/accounts.js
@@ -55,7 +55,8 @@
validProfiles : validProfiles,
invalidProfiles : invalidProfiles,
isNativeClient: gon.isNativeClient,
- musician: context.JK.currentUserMusician
+ musician: context.JK.currentUserMusician,
+ sales_count: userDetail.sales_count
} , { variable: 'data' }));
$('#account-content-scroller').html($template);
@@ -113,7 +114,7 @@
// License dialog:
$("#account-content-scroller").on('click', '#account-view-license-link', function(evt) {evt.stopPropagation(); app.layout.showDialog('jamtrack-license-dialog'); return false; } );
- $("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); app.layout.showDialog('jamtrack-payment-history-dialog'); return false; } );
+ $("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); navToPaymentHistory(); return false; } );
}
function renderAccount() {
@@ -157,6 +158,10 @@
window.location = "/client#/account/audio"
}
+ function navToPaymentHistory() {
+ window.location = '/client#/account/paymentHistory'
+ }
+
// handle update avatar event
function updateAvatar(avatar_url) {
var photoUrl = context.JK.resolveAvatarUrl(avatar_url);
diff --git a/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee b/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee
new file mode 100644
index 000000000..2f20246cf
--- /dev/null
+++ b/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee
@@ -0,0 +1,180 @@
+$ = jQuery
+context = window
+context.JK ||= {}
+
+context.JK.AccountPaymentHistoryScreen = class AccountPaymentHistoryScreen
+ LIMIT = 20
+
+ constructor: (@app) ->
+ @logger = context.JK.logger
+ @rest = context.JK.Rest()
+ @screen = null
+ @scroller = null
+ @genre = null
+ @artist = null
+ @instrument = null
+ @availability = null
+ @nextPager = null
+ @noMoreSales = null
+ @currentPage = 0
+ @next = null
+ @tbody = null
+ @rowTemplate = null
+
+ beforeShow:(data) =>
+
+
+ afterShow:(data) =>
+ @refresh()
+
+ events:() =>
+ @backBtn.on('click', @onBack)
+
+ onBack:() =>
+ window.location = '/client#/account'
+ return false
+
+ clearResults:() =>
+ @currentPage = 0
+ @tbody.empty()
+ @noMoreSales.hide()
+ @next = null
+
+
+
+ refresh:() =>
+ @currentQuery = this.buildQuery()
+ @rest.getSalesHistory(@currentQuery)
+ .done(@salesHistoryDone)
+ .fail(@salesHistoryFail)
+
+
+ renderPayments:(response) =>
+ if response.entries? && response.entries.length > 0
+ for sale in response.entries
+ amt = sale.recurly_total_in_cents
+ amt = 0 if !amt?
+
+ original_total = sale.state.original_total
+ refund_total = sale.state.refund_total
+
+ refund_state = null
+ if original_total != 0 # the enclosed logic does not work for free purchases
+ if refund_total == original_total
+ refund_state = 'refunded'
+ else if refund_total != 0 and refund_total < original_total
+ refund_state = 'partial refund'
+
+
+ displayAmount = (amt/100).toFixed(2)
+ status = 'paid'
+
+ if sale.state.voided
+ status = 'voided'
+ displayAmount = (0).toFixed(2)
+ else if refund_state?
+ status = refund_state
+ displayAmount = (amt/100).toFixed(2) + " (refunded: #{(refund_total/100).toFixed(2)})"
+
+ description = []
+ for line_item in sale.line_items
+ description.push(line_item.product_info?.name)
+
+ payment = {
+ date: context.JK.formatDate(sale.created_at, true)
+ amount: displayAmount
+ status: status
+ payment_method: 'Credit Card',
+ description: description.join(', ')
+ }
+
+ tr = $(context._.template(@rowTemplate, payment, { variable: 'data' }));
+ @tbody.append(tr);
+ else
+ tr = "
| No payments found |
"
+ @tbody.append(tr);
+
+ salesHistoryDone:(response) =>
+
+ # Turn in to HTML rows and append:
+ #@tbody.html("")
+ console.log("response.next", response)
+ @next = response.next_page
+ @renderPayments(response)
+ if response.next_page == null
+ # if we less results than asked for, end searching
+ @scroller.infinitescroll 'pause'
+ @logger.debug("end of history")
+ if @currentPage > 0
+ @noMoreSales.show()
+ # there are bugs with infinitescroll not removing the 'loading'.
+ # it's most noticeable at the end of the list, so whack all such entries
+ $('.infinite-scroll-loader').remove()
+ else
+ @currentPage++
+ this.buildQuery()
+ this.registerInfiniteScroll()
+
+
+ salesHistoryFail:(jqXHR)=>
+ @noMoreSales.show()
+ @app.notifyServerError jqXHR, 'Payment History Unavailable'
+
+ defaultQuery:() =>
+ query =
+ per_page: LIMIT
+ page: @currentPage+1
+ if @next
+ query.since = @next
+ query
+
+ buildQuery:() =>
+ @currentQuery = this.defaultQuery()
+
+
+ registerInfiniteScroll:() =>
+ that = this
+ @scroller.infinitescroll {
+ behavior: 'local'
+ navSelector: '#account-payment-history .btn-next-pager'
+ nextSelector: '#account-payment-history .btn-next-pager'
+ binder: @scroller
+ dataType: 'json'
+ appendCallback: false
+ prefill: false
+ bufferPx: 100
+ loading:
+ msg: $('Loading ...
')
+ img: '/assets/shared/spinner.gif'
+ path: (page) =>
+ '/api/sales?' + $.param(that.buildQuery())
+
+ }, (json, opts) =>
+ this.salesHistoryDone(json)
+ @scroller.infinitescroll 'resume'
+
+ initialize:() =>
+ screenBindings =
+ 'beforeShow': this.beforeShow
+ 'afterShow': this.afterShow
+ @app.bindScreen 'account/paymentHistory', screenBindings
+ @screen = $('#account-payment-history')
+ @scroller = @screen.find('.content-body-scroller')
+ @nextPager = @screen.find('a.btn-next-pager')
+ @noMoreSales = @screen.find('.end-of-payments-list')
+ @tbody = @screen.find("table.payment-table tbody")
+ @rowTemplate = $('#template-payment-history-row').html()
+ @backBtn = @screen.find('.back')
+
+ if @screen.length == 0
+ throw new Error('@screen must be specified')
+ if @scroller.length == 0
+ throw new Error('@scroller must be specified')
+ if @tbody.length == 0
+ throw new Error('@tbody must be specified')
+ if @noMoreSales.length == 0
+ throw new Error('@noMoreSales must be specified')
+
+ this.events()
+
+
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 43dff7227..0cf7b6767 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -1499,9 +1499,18 @@
dataType: "json",
contentType: 'application/json'
});
- }
+ }
- function getBackingTracks(options) {
+ function getSalesHistory(options) {
+ return $.ajax({
+ type: "GET",
+ url: '/api/sales?' + $.param(options),
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function getBackingTracks(options) {
return $.ajax({
type: "GET",
url: '/api/backing_tracks?' + $.param(options),
@@ -1765,6 +1774,7 @@
this.getJamtracks = getJamtracks;
this.getPurchasedJamTracks = getPurchasedJamTracks;
this.getPaymentHistory = getPaymentHistory;
+ this.getSalesHistory = getSalesHistory;
this.getJamTrackRight = getJamTrackRight;
this.enqueueJamTrack = enqueueJamTrack;
this.getBackingTracks = getBackingTracks;
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index 3fb56cc43..b91ab71af 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -621,11 +621,12 @@
}
// returns Fri May 20, 2013
- context.JK.formatDate = function (dateString) {
+ context.JK.formatDate = function (dateString, suppressDay) {
var date = new Date(dateString);
- return days[date.getDay()] + ' ' + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear();
+ return (suppressDay ? '' : (days[date.getDay()] + ' ')) + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear();
}
+
context.JK.formatDateYYYYMMDD = function(dateString) {
var date = new Date(dateString);
return date.getFullYear() + '-' + context.JK.padString((date.getMonth() + 1).toString(), 2) + '-' + context.JK.padString(date.getDate(), 2);
diff --git a/web/app/assets/stylesheets/client/accountPaymentHistory.css.scss b/web/app/assets/stylesheets/client/accountPaymentHistory.css.scss
new file mode 100644
index 000000000..f0a7c1fdf
--- /dev/null
+++ b/web/app/assets/stylesheets/client/accountPaymentHistory.css.scss
@@ -0,0 +1,51 @@
+@import 'common.css.scss';
+
+#account-payment-history {
+
+ .content-body-scroller {
+ padding:20px;
+ @include border_box_sizing;
+ }
+
+ table td.loading {
+ text-align:center;
+ }
+
+ .end-of-list {
+ margin-top:20px;
+ }
+ td {
+
+ &.amount {
+ }
+
+ &.voided {
+
+ text-decoration:line-through;
+ }
+ }
+
+ .account-left {
+ float: left;
+ min-width: 165px;
+ width: 20%;
+ }
+
+ .account-left h2 {
+ color: #FFFFFF;
+ font-size: 23px;
+ font-weight: 400;
+ margin-bottom: 20px;
+ }
+
+ .input-aligner {
+ margin: 10px 14px 20px 0;
+ text-align:right;
+
+ .back {
+ margin-right:22px;
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index 28e2f4ed5..5bb85acaa 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -31,6 +31,7 @@
*= require ./findSession
*= require ./session
*= require ./account
+ *= require ./accountPaymentHistory
*= require ./search
*= require ./ftue
*= require ./jamServer
diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss
index dbaaa80cf..75edd1bc1 100644
--- a/web/app/assets/stylesheets/client/common.css.scss
+++ b/web/app/assets/stylesheets/client/common.css.scss
@@ -330,3 +330,8 @@ $fair: #cc9900;
border-radius:8px;
}
+
+.capitalize {
+ text-transform: capitalize
+}
+
diff --git a/web/app/assets/stylesheets/client/jamtrack.css.scss b/web/app/assets/stylesheets/client/jamtrack.css.scss
index b9ed8314b..979ac5377 100644
--- a/web/app/assets/stylesheets/client/jamtrack.css.scss
+++ b/web/app/assets/stylesheets/client/jamtrack.css.scss
@@ -240,8 +240,4 @@
.jamtrack_buttons {
margin: 8px 4px 12px 4px;
-}
-
-.capitalize {
- text-transform: capitalize
}
\ No newline at end of file
diff --git a/web/app/controllers/api_sales_controller.rb b/web/app/controllers/api_sales_controller.rb
new file mode 100644
index 000000000..8886ba6bd
--- /dev/null
+++ b/web/app/controllers/api_sales_controller.rb
@@ -0,0 +1,15 @@
+class ApiSalesController < ApiController
+
+ respond_to :json
+
+ def index
+ data = Sale.index(current_user,
+ page: params[:page],
+ per_page: params[:per_page])
+
+
+ @sales = data[:query]
+ @next = data[:next_page]
+ render "api_sales/index", :layout => nil
+ end
+end
\ No newline at end of file
diff --git a/web/app/views/api_sales/index.rabl b/web/app/views/api_sales/index.rabl
new file mode 100644
index 000000000..1b61714f5
--- /dev/null
+++ b/web/app/views/api_sales/index.rabl
@@ -0,0 +1,7 @@
+node :next_page do |page|
+ @next
+end
+
+node :entries do |page|
+ partial "api_sales/show", object: @sales
+end
\ No newline at end of file
diff --git a/web/app/views/api_sales/show.rabl b/web/app/views/api_sales/show.rabl
new file mode 100644
index 000000000..d6d7129a6
--- /dev/null
+++ b/web/app/views/api_sales/show.rabl
@@ -0,0 +1,11 @@
+object @sale
+
+attributes :id, :recurly_invoice_id, :recurly_subtotal_in_cents, :recurly_tax_in_cents, :recurly_total_in_cents, :recurly_currency, :sale_type, :recurly_invoice_number, :state, :created_at
+
+child(:recurly_transactions => :recurly_transactions) {
+ attributes :transaction_type, :amount_in_cents
+}
+
+child(:sale_line_items => :line_items) {
+ attributes :id, :product_info
+}
diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl
index 2e50c7dcb..32c79fee0 100644
--- a/web/app/views/api_users/show.rabl
+++ b/web/app/views/api_users/show.rabl
@@ -1,6 +1,6 @@
object @user
-attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :reuse_card, :purchased_jamtracks_count
+attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count
if @user.musician?
node :location do @user.location end
@@ -10,7 +10,7 @@ end
# give back more info if the user being fetched is yourself
if @user == current_user
- attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications
+ attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count
node :geoiplocation do |user|
geoiplocation = current_user.geoiplocation
diff --git a/web/app/views/clients/_account.html.erb b/web/app/views/clients/_account.html.erb
index a630ef918..ceb703681 100644
--- a/web/app/views/clients/_account.html.erb
+++ b/web/app/views/clients/_account.html.erb
@@ -118,7 +118,13 @@
-
View Payment History
+
+ {% if (data.sales_count == 0) { %}
+ You have made no purchases.
+ {% } else { %}
+ You have made {{data.sales_count}} purchase{{data.sales_count == 1 ? '' : 's'}}.
+ {% } %}
+
diff --git a/web/app/views/clients/_account_payment_history.html.slim b/web/app/views/clients/_account_payment_history.html.slim
new file mode 100644
index 000000000..5a31dffaf
--- /dev/null
+++ b/web/app/views/clients/_account_payment_history.html.slim
@@ -0,0 +1,44 @@
+.screen.secondary layout="screen" layout-id="account/paymentHistory" class="screen secondary" id="account-payment-history"
+
+ .content
+ .content-head
+ .content-icon=image_tag("content/icon_account.png", height:20, width:27 )
+ h1 my account
+ =render "screen_navigation"
+ .content-body
+ .content-body-scroller
+
+ .account-left
+ h2 payment history:
+ table.payment-table
+ thead
+ tr
+ th DATE
+ th METHOD
+ th DESCRIPTION
+ th STATUS
+ th AMOUNT
+ tbody
+ a.btn-next-pager href="/api/sales?page=1" Next
+ .end-of-payments-list.end-of-list="No more payment history"
+
+
+ .input-aligner
+ a.back href="" class="button-grey" BACK
+ br clear="all"
+
+
+
+
+script#template-payment-history-row type="text/template"
+ tr
+ td
+ | {{data.date}}
+ td.capitalize
+ | {{data.payment_method}}
+ td
+ | {{data.description}}
+ td.capitalize
+ | {{data.status}}
+ td.amount class="{{data.status}}"
+ | ${{data.amount}}
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 958c6aa99..fe8b7a103 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -58,6 +58,7 @@
<%= render "account_jamtracks" %>
<%= render "account_session_detail" %>
<%= render "account_session_properties" %>
+<%= render "account_payment_history" %>
<%= render "inviteMusicians" %>
<%= render "hoverBand" %>
<%= render "hoverFan" %>
@@ -214,6 +215,9 @@
var accountAudioProfile = new JK.AccountAudioProfile(JK.app);
accountAudioProfile.initialize();
+ var accountPaymentHistoryScreen = new JK.AccountPaymentHistoryScreen(JK.app);
+ accountPaymentHistoryScreen.initialize();
+
var searchResultScreen = new JK.SearchResultScreen(JK.app);
searchResultScreen.initialize();
diff --git a/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim b/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim
index 663d0f939..64b0918e2 100644
--- a/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim
+++ b/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim
@@ -18,16 +18,3 @@
.jamtrack_buttons
.right
a.button-orange class='btnCancel' layout-action='cancel' OK
-
- script#template-payment-history-row type="text/template"
- tr
- td
- | {{data.date}}
- td
- | ${{data.amount}}
- td.capitalize
- | {{data.status}}
- td.capitalize
- | {{data.payment_method}}
- td
- | {{data.reference}}
diff --git a/web/config/routes.rb b/web/config/routes.rb
index 7cdbce911..b308d918a 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -276,6 +276,9 @@ SampleApp::Application.routes.draw do
match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
+ # sale info
+ match '/sales' => 'api_sales#index', :via => :get
+
# login/logout
match '/auth_session' => 'api_users#auth_session_create', :via => :post
match '/auth_session' => 'api_users#auth_session_delete', :via => :delete
diff --git a/web/spec/controllers/api_sales_controller_spec.rb b/web/spec/controllers/api_sales_controller_spec.rb
new file mode 100644
index 000000000..e1e39bf32
--- /dev/null
+++ b/web/spec/controllers/api_sales_controller_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe ApiSalesController do
+ render_views
+
+ let(:user) {FactoryGirl.create(:user)}
+ let(:jam_track) {FactoryGirl.create(:jam_track)}
+
+ before(:each) do
+ controller.current_user = user
+ end
+
+ describe "index" do
+
+ it "empty" do
+ get :index, { :format => 'json'}
+
+ response.should be_success
+ body = JSON.parse(response.body)
+ body['next_page'].should be_nil
+ body['entries'].should eq([])
+ end
+
+ it "one item" do
+ sale = Sale.create_jam_track_sale(user)
+ sale.recurly_invoice_id = SecureRandom.uuid
+ sale.save!
+
+ shopping_cart = ShoppingCart.create(user, jam_track)
+ sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
+
+ get :index, { :format => 'json'}
+ response.should be_success
+ body = JSON.parse(response.body)
+ body['next_page'].should be_nil
+ entries = body['entries']
+ entries.should have(1).items
+ sale_entry = entries[0]
+ sale_entry["line_items"].should have(1).items
+ sale_entry["recurly_transactions"].should have(0).items
+
+
+ transaction = FactoryGirl.create(:recurly_transaction_web_hook, invoice_id: sale.recurly_invoice_id, transaction_type: RecurlyTransactionWebHook::VOID)
+
+ get :index, { :format => 'json'}
+ response.should be_success
+ body = JSON.parse(response.body)
+ body['next_page'].should be_nil
+ entries = body['entries']
+ entries.should have(1).items
+ sale_entry = entries[0]
+ sale_entry["line_items"].should have(1).items
+ sale_entry["recurly_transactions"].should have(1).items
+ end
+ end
+
+end
diff --git a/web/spec/factories.rb b/web/spec/factories.rb
index d625ec53c..ca49f36eb 100644
--- a/web/spec/factories.rb
+++ b/web/spec/factories.rb
@@ -756,4 +756,25 @@ FactoryGirl.define do
bpm 120
tap_in_count 3
end
+
+ factory :recurly_transaction_web_hook, :class => JamRuby::RecurlyTransactionWebHook do
+
+ transaction_type JamRuby::RecurlyTransactionWebHook::SUCCESSFUL_PAYMENT
+ sequence(:recurly_transaction_id ) { |n| "recurly-transaction-id-#{n}" }
+ sequence(:subscription_id ) { |n| "subscription-id-#{n}" }
+ sequence(:invoice_id ) { |n| "invoice-id-#{n}" }
+ sequence(:invoice_number ) { |n| 1000 + n }
+ invoice_number_prefix nil
+ action 'purchase'
+ status 'success'
+ transaction_at Time.now
+ amount_in_cents 199
+ reference 100000
+ message 'meh'
+ association :user, factory: :user
+
+ factory :recurly_transaction_web_hook_failed do
+ transaction_type JamRuby::RecurlyTransactionWebHook::FAILED_PAYMENT
+ end
+ end
end
diff --git a/web/spec/features/account_spec.rb b/web/spec/features/account_spec.rb
index 80c2ab1b0..15b7974aa 100644
--- a/web/spec/features/account_spec.rb
+++ b/web/spec/features/account_spec.rb
@@ -5,6 +5,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
let(:user) { FactoryGirl.create(:user) }
+ let(:jam_track) {FactoryGirl.create(:jam_track)}
before(:each) do
UserMailer.deliveries.clear
@@ -135,6 +136,26 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
}
end
end
+ end
+
+ describe "payment history" do
+
+ it "show 1 sale" do
+
+ sale = Sale.create_jam_track_sale(user)
+ shopping_cart = ShoppingCart.create(user, jam_track)
+ sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, 'some_adjustment_uuid', nil)
+
+ visit "/client#/account"
+
+ find('.account-mid.payments', text: 'You have made 1 purchase.')
+
+ find("#account-payment-history-link").trigger(:click)
+ find('h2', text: 'payment history:')
+ find('table tr td', text: '$0.00') # 1st purchase is free
+
+ end
+
end