diff --git a/app/templates/admin/analytics-subscriptions.jade b/app/templates/admin/analytics-subscriptions.jade
index 54d4dfd5e..b45188478 100644
--- a/app/templates/admin/analytics-subscriptions.jade
+++ b/app/templates/admin/analytics-subscriptions.jade
@@ -91,6 +91,7 @@ block content
             th Started
             th Cancelled
             th Net
+            th Ended
         tbody
           each sub in subs
             tr
@@ -99,3 +100,4 @@ block content
               td= sub.started
               td= sub.cancelled
               td= sub.started - sub.cancelled
+              td= sub.ended
diff --git a/app/views/admin/AnalyticsSubscriptionsView.coffee b/app/views/admin/AnalyticsSubscriptionsView.coffee
index 454b15f57..e308707f0 100644
--- a/app/views/admin/AnalyticsSubscriptionsView.coffee
+++ b/app/views/admin/AnalyticsSubscriptionsView.coffee
@@ -87,16 +87,24 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
           subDayMap[cancelDay] ?= {}
           subDayMap[cancelDay]['cancel'] ?= 0
           subDayMap[cancelDay]['cancel']++
+        if endDay = sub?.end?.substring(0, 10)
+          subDayMap[endDay] ?= {}
+          subDayMap[endDay]['end'] ?= 0
+          subDayMap[endDay]['end']++
+      today = new Date().toISOString().substring(0, 10)
       for day of subDayMap
+        continue if day > today
         @subs.push
           day: day
           started: subDayMap[day]['start'] or 0
           cancelled: subDayMap[day]['cancel'] or 0
+          ended: subDayMap[day]['end'] or 0
 
       @subs.sort (a, b) -> a.day.localeCompare(b.day)
       startedLastMonth = 0
       for sub, i in @subs
         @total += sub.started
+        @total -= sub.ended
         @cancelled += sub.cancelled
         sub.total = @total
         startedLastMonth += sub.started if @subs.length - i < 31
@@ -229,7 +237,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
       lineColor: lineMetadata[startedSubsID].color
       strokeWidth: lineMetadata[startedSubsID].strokeWidth
       min: 0
-      max: d3.max(@subs, (d) -> d.started)
+      max: d3.max(@subs[-timeframeDays..], (d) -> d.started + 2)
 
     ## Total subs target
 
@@ -256,21 +264,29 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
       max: @targetSubCount
 
     ## Cancelled
+
+    # TODO: move this average cancelled stuff up the chain
     averageCancelled = 0
 
     # Build line data
     levelPoints = []
     cancelled = []
-    for sub, i in @subs
-      if i >= @subs.length - 30
-        cancelled.push sub.cancelled
+    for sub, i in @subs[@subs.length - 30...]
+      cancelled.push sub.cancelled
       levelPoints.push
-        x: i
+        x: @subs.length - 30 + i
         y: sub.cancelled
         day: sub.day
+        pointID: "#{cancelledSubsID}#{@subs.length - 30 + i}"
+        values: []
+    averageCancelled = cancelled.reduce((a, b) -> a + b) / cancelled.length
+    for sub, i in @subs[0...-30]
+      levelPoints.splice i, 0,
+        x: i
+        y: averageCancelled
+        day: sub.day
         pointID: "#{cancelledSubsID}#{i}"
         values: []
-    averageCancelled = cancelled.reduce((a, b) -> a + b) / cancelled.length
 
     # Ensure points for each day
     for day, i in days
@@ -293,7 +309,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
       lineColor: lineMetadata[cancelledSubsID].color
       strokeWidth: lineMetadata[cancelledSubsID].strokeWidth
       min: 0
-      max: d3.max(@subs, (d) -> d.started)
+      max: d3.max(@subs[-timeframeDays..], (d) -> d.started + 2)
 
     ## 7-Day Net Subs
 
@@ -338,8 +354,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
       lineColor: lineMetadata[netSubsID].color
       strokeWidth: lineMetadata[netSubsID].strokeWidth
       min: 0
-      max: d3.max(@subs, (d) -> d.started)
-
+      max: d3.max(@subs[-timeframeDays..], (d) -> d.started + 2)
 
   updateAnalyticsGraphs: ->
     # Build d3 graphs
diff --git a/server/payments/subscription_handler.coffee b/server/payments/subscription_handler.coffee
index d8fc6ea52..5c81df700 100644
--- a/server/payments/subscription_handler.coffee
+++ b/server/payments/subscription_handler.coffee
@@ -90,60 +90,83 @@ class SubscriptionHandler extends Handler
 
   getSubscriptions: (req, res) ->
     # Returns a list of active subscriptions
-    # TODO: does not handle customers with 11+ active subscriptions
     # TODO: does not track sponsored subs, only basic
     # TODO: does not return free subs
     # TODO: add tests
     # TODO: aggregate this data daily instead of providing it on demand
     # TODO: take date range as input
+    # TODO: are ended counts correct for today?  E.g. retries may complicate things.
 
     return @sendForbiddenError(res) unless req.user and req.user.isAdmin()
 
-    # @subs ?= []
     # return @sendSuccess(res, @subs) unless _.isEmpty(@subs)
-    @subs = []
+    @subMap = {}
 
-    customersProcessed = 0
-    nextBatch = (starting_after, done) =>
+    console.log 'Fetching invoices...'
+
+    processInvoices = (starting_after, done) =>
       options = limit: 100
       options.starting_after = starting_after if starting_after
-      stripe.customers.list options, (err, customers) =>
+      stripe.invoices.list options, (err, invoices) =>
         return done(err) if err
-        customersProcessed += customers.data.length
-
-        for customer in customers.data
-          continue unless customer?.subscriptions?.data?.length > 0
-          for subscription in customer.subscriptions.data
-            continue unless subscription.plan.id is 'basic'
-
-            amount = subscription.plan.amount
-            if subscription?.discount?.coupon?
-              if subscription.discount.coupon.percent_off
-                amount = amount *  (100 - subscription.discount.coupon.percent_off) / 100;
-              else if subscription.discount.coupon.amount_off
-                amount -= subscription.discount.coupon.amount_off
-            else if customer.discount?.coupon?
-              if customer.discount.coupon.percent_off
-                amount = amount *  (100 - customer.discount.coupon.percent_off) / 100
-              else if customer.discount.coupon.amount_off
-                amount -= customer.discount.coupon.amount_off
-
-            continue unless amount > 0
-
-            sub = start: new Date(subscription.start * 1000)
-            if subscription.cancel_at_period_end
-              sub.cancel = new Date(subscription.canceled_at * 1000)
-              sub.end = new Date(subscription.current_period_end * 1000)
-            @subs.push(sub)
-
-        # Can't fetch all the test Stripe data
-        if customers.has_more and (config.isProduction or customersProcessed < 500)
-          return nextBatch(customers.data[customers.data.length - 1].id, done)
+        for invoice in invoices.data
+          continue unless invoice.paid
+          continue unless invoice.subscription
+          continue unless invoice.total > 0
+          continue unless invoice.lines?.data?[0]?.plan?.id is 'basic'
+          subID = invoice.subscription
+          invoiceDate = new Date(invoice.date * 1000)
+          if subID of @subMap
+            @subMap[subID].first = invoiceDate
+          else
+            @subMap[subID] =
+              first: invoiceDate
+              last: invoiceDate
+              customerID: invoice.customer
+        if invoices.has_more
+          console.log 'Fetching more invoices', Object.keys(@subMap).length
+          return processInvoices(invoices.data[invoices.data.length - 1].id, done)
         else
           return done()
-    nextBatch null, (err) =>
+
+    processInvoices null, (err) =>
       return @sendDatabaseError(res, err) if err
-      @sendSuccess(res, @subs)
+
+      console.log 'Checking cancelled subscriptions...'
+
+      createCheckCancelledFn = (customerID, subscriptionID) =>
+        (done) =>
+          stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) =>
+            return done() if err
+            if subscription?.cancel_at_period_end
+              @subMap[subscriptionID].cancel = new Date(subscription.canceled_at * 1000)
+            done()
+
+      tasks = []
+      for subID of @subMap
+        expectedLastPayment = new Date(@subMap[subID].last)
+        expectedLastPayment.setUTCFullYear(new Date().getUTCFullYear())
+        expectedLastPayment.setUTCMonth(new Date().getUTCMonth() - 1)
+        expectedLastPayment.setUTCDate(new Date().getUTCDate() - 8) # In case last payment had some retries
+        if @subMap[subID].last > expectedLastPayment
+          tasks.push createCheckCancelledFn(@subMap[subID].customerID, subID)
+
+      async.parallel tasks, (err, results) =>
+        return @sendDatabaseError(res, err) if err
+        @subs = []
+        for subID of @subMap
+          sub =
+            start: @subMap[subID].first
+            subID: subID
+            customerID: @subMap[subID].customerID
+          sub.cancel = @subMap[subID].cancel if @subMap[subID].cancel
+          oneMonthAgo = new Date()
+          oneMonthAgo.setUTCMonth(oneMonthAgo.getUTCMonth() - 1)
+          if @subMap[subID].last < oneMonthAgo
+            sub.end = new Date(@subMap[subID].last)
+            sub.end.setUTCMonth(sub.end.getUTCMonth() + 1)
+          @subs.push sub
+        @sendSuccess(res, @subs)
 
   subscribeUser: (req, user, done) ->
     if (not req.user) or req.user.isAnonymous() or user.isAnonymous()