From ab2925ef88c2476c1aef2e95f915efb51309cc19 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 12 Apr 2015 13:45:26 -0500 Subject: [PATCH] VRFS-2821 - payment history screen added --- .../models/recurly_transaction_web_hook.rb | 15 ++ ruby/lib/jam_ruby/models/sale.rb | 63 +++++- ruby/lib/jam_ruby/models/sale_line_item.rb | 48 ++++- ruby/lib/jam_ruby/models/user.rb | 4 + ruby/lib/jam_ruby/recurly_client.rb | 15 +- .../jam_ruby/models/sale_line_item_spec.rb | 41 ++++ ruby/spec/jam_ruby/models/sale_spec.rb | 40 ++++ .../jam_ruby/models/shopping_cart_spec.rb | 3 +- web/app/assets/javascripts/accounts.js | 9 +- .../accounts_payment_history_screen.js.coffee | 180 ++++++++++++++++++ web/app/assets/javascripts/jam_rest.js | 14 +- web/app/assets/javascripts/utils.js | 5 +- .../client/accountPaymentHistory.css.scss | 51 +++++ web/app/assets/stylesheets/client/client.css | 1 + .../assets/stylesheets/client/common.css.scss | 5 + .../stylesheets/client/jamtrack.css.scss | 4 - web/app/controllers/api_sales_controller.rb | 15 ++ web/app/views/api_sales/index.rabl | 7 + web/app/views/api_sales/show.rabl | 11 ++ web/app/views/api_users/show.rabl | 4 +- web/app/views/clients/_account.html.erb | 8 +- .../_account_payment_history.html.slim | 44 +++++ web/app/views/clients/index.html.erb | 4 + .../_jamtrackPaymentHistoryDialog.html.slim | 13 -- web/config/routes.rb | 3 + .../controllers/api_sales_controller_spec.rb | 57 ++++++ web/spec/factories.rb | 21 ++ web/spec/features/account_spec.rb | 21 ++ 28 files changed, 665 insertions(+), 41 deletions(-) create mode 100644 ruby/spec/jam_ruby/models/sale_line_item_spec.rb create mode 100644 web/app/assets/javascripts/accounts_payment_history_screen.js.coffee create mode 100644 web/app/assets/stylesheets/client/accountPaymentHistory.css.scss create mode 100644 web/app/controllers/api_sales_controller.rb create mode 100644 web/app/views/api_sales/index.rabl create mode 100644 web/app/views/api_sales/show.rabl create mode 100644 web/app/views/clients/_account_payment_history.html.slim create mode 100644 web/spec/controllers/api_sales_controller_spec.rb 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