Skip to content

Subscription Management

Complete subscription lifecycle management with billing oversight, plan changes, and revenue tracking.

Preview

Contact Form Component Preview

Overview

The subscription management interface provides administrators with comprehensive control over user subscriptions, billing operations, and revenue optimization.

Subscription List Interface

Data Fetching

typescript
const { data: subscriptionsData, refresh: refreshSubscriptions } = await useFetch('/api/admin/subscriptions', {
  query: { 
    page: currentPage.value,
    limit: itemsPerPage.value,
    status: selectedStatus.value,
    plan: selectedPlan.value,
    dateRange: dateRange.value
  }
})

Subscription Metrics

Real-time subscription statistics:

typescript
const subscriptionStats = computed(() => ({
  total: subscriptionsData.value?.total || 0,
  active: subscriptionsData.value?.subscriptions?.filter(s => s.status === 'active').length || 0,
  trialing: subscriptionsData.value?.subscriptions?.filter(s => s.status === 'trialing').length || 0,
  pastDue: subscriptionsData.value?.subscriptions?.filter(s => s.status === 'past_due').length || 0,
  canceled: subscriptionsData.value?.subscriptions?.filter(s => s.status === 'canceled').length || 0,
  totalRevenue: calculateTotalRevenue(subscriptionsData.value?.subscriptions || []),
  mrr: calculateMRR(subscriptionsData.value?.subscriptions || [])
}))

const calculateTotalRevenue = (subscriptions) => {
  return subscriptions.reduce((sum, sub) => {
    return sum + (sub.status === 'active' ? (sub.planPrice / 100) : 0)
  }, 0)
}

const calculateMRR = (subscriptions) => {
  return subscriptions.reduce((sum, sub) => {
    if (sub.status === 'active' && sub.interval === 'month') {
      return sum + (sub.planPrice / 100)
    } else if (sub.status === 'active' && sub.interval === 'year') {
      return sum + (sub.planPrice / 100 / 12)
    }
    return sum
  }, 0)
}

Revenue Dashboard

Revenue Overview Cards

typescript
const revenueMetrics = computed(() => ({
  totalRevenue: subscriptionStats.value.totalRevenue,
  mrr: subscriptionStats.value.mrr,
  arr: subscriptionStats.value.mrr * 12,
  averageRevenuePerUser: subscriptionStats.value.totalRevenue / subscriptionStats.value.active,
  churnRate: calculateChurnRate(),
  growthRate: calculateGrowthRate()
}))

Revenue Chart

Interactive revenue visualization:

typescript
const createRevenueChart = () => {
  const ctx = revenueChart.value?.getContext('2d')
  if (!ctx) return

  revenueChartInstance = new Chart(ctx, {
    type: 'line',
    data: {
      labels: revenueData.value.map(d => 
        new Date(d.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
      ),
      datasets: [{
        label: 'Daily Revenue ($)',
        data: revenueData.value.map(d => d.revenue),
        borderColor: '#000000',
        backgroundColor: 'rgba(0, 0, 0, 0.1)',
        tension: 0.4,
        fill: true
      }]
    },
    options: {
      responsive: true,
      plugins: {
        legend: {
          display: true,
          position: 'top'
        }
      },
      scales: {
        y: {
          beginAtZero: true,
          ticks: {
            callback: (value) => `$${value}`
          }
        }
      }
    }
  })
}

Subscription Actions

Individual Subscription Management

typescript
const subscriptionActions = [
  {
    label: 'View Details',
    icon: 'eye',
    action: (subscription) => showSubscriptionDetails(subscription),
    color: 'blue'
  },
  {
    label: 'Change Plan',
    icon: 'refresh',
    action: (subscription) => changePlan(subscription),
    color: 'green',
    condition: (sub) => sub.status === 'active'
  },
  {
    label: 'Cancel Subscription',
    icon: 'x',
    action: (subscription) => cancelSubscription(subscription),
    color: 'red',
    condition: (sub) => sub.status === 'active'
  },
  {
    label: 'Resume Subscription',
    icon: 'play',
    action: (subscription) => resumeSubscription(subscription),
    color: 'green',
    condition: (sub) => sub.status === 'canceled' && !sub.cancel_at_period_end
  }
]

Cancel Subscription

typescript
const cancelSubscription = async (subscription) => {
  const options = await showCancelModal(subscription)
  
  if (options) {
    try {
      await $fetch('/api/subscription/cancel', {
        method: 'POST',
        body: { 
          subscriptionId: subscription.stripe_subscription_id,
          immediately: options.immediately,
          reason: options.reason
        }
      })
      
      refreshSubscriptions()
      toast.success('Subscription canceled successfully')
    } catch (error) {
      toast.error('Failed to cancel subscription')
    }
  }
}

const showCancelModal = (subscription) => {
  return new Promise((resolve) => {
    const modal = {
      title: 'Cancel Subscription',
      subscription,
      options: {
        immediately: false,
        reason: ''
      },
      onConfirm: (options) => resolve(options),
      onCancel: () => resolve(null)
    }
    
    cancelModal.value = modal
    showCancelModal.value = true
  })
}

Resume Subscription

typescript
const resumeSubscription = async (subscription) => {
  const confirmed = await showConfirmModal(
    'Resume Subscription',
    `Resume ${subscription.user.full_name}'s ${subscription.plan.name} subscription?`
  )
  
  if (confirmed) {
    try {
      await $fetch('/api/subscription/resume', {
        method: 'POST',
        body: { subscriptionId: subscription.stripe_subscription_id }
      })
      
      refreshSubscriptions()
      toast.success('Subscription resumed successfully')
    } catch (error) {
      toast.error('Failed to resume subscription')
    }
  }
}

Plan Changes

typescript
const changePlan = async (subscription) => {
  const newPlan = await showPlanChangeModal(subscription)
  
  if (newPlan) {
    try {
      await $fetch('/api/subscription/change-plan', {
        method: 'POST',
        body: { 
          subscriptionId: subscription.stripe_subscription_id,
          newPriceId: newPlan.stripe_price_id,
          prorate: true
        }
      })
      
      refreshSubscriptions()
      toast.success('Plan changed successfully')
    } catch (error) {
      toast.error('Failed to change plan')
    }
  }
}

Billing Management

Payment Issues

Handle failed payments and past due subscriptions:

typescript
const pastDueSubscriptions = computed(() => 
  subscriptionsData.value?.subscriptions?.filter(s => s.status === 'past_due') || []
)

const retryPayment = async (subscription) => {
  try {
    await $fetch('/api/billing/retry-payment', {
      method: 'POST',
      body: { subscriptionId: subscription.stripe_subscription_id }
    })
    
    refreshSubscriptions()
    toast.success('Payment retry initiated')
  } catch (error) {
    toast.error('Failed to retry payment')
  }
}

Refund Processing

typescript
const processRefund = async (subscription, amount = null) => {
  const refundData = await showRefundModal(subscription, amount)
  
  if (refundData) {
    try {
      await $fetch('/api/billing/refund', {
        method: 'POST',
        body: {
          subscriptionId: subscription.stripe_subscription_id,
          amount: refundData.amount,
          reason: refundData.reason
        }
      })
      
      refreshSubscriptions()
      toast.success('Refund processed successfully')
    } catch (error) {
      toast.error('Failed to process refund')
    }
  }
}

Subscription Analytics

Churn Analysis

typescript
const churnAnalysis = computed(() => {
  const now = new Date()
  const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
  const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1)
  
  const canceledThisMonth = subscriptionsData.value?.subscriptions?.filter(s => 
    s.status === 'canceled' && 
    new Date(s.canceled_at) >= thisMonth
  ).length || 0
  
  const activeLastMonth = subscriptionsData.value?.subscriptions?.filter(s => 
    s.status === 'active' || 
    (s.status === 'canceled' && new Date(s.canceled_at) >= thisMonth)
  ).length || 0
  
  return {
    churnRate: activeLastMonth > 0 ? (canceledThisMonth / activeLastMonth) * 100 : 0,
    churned: canceledThisMonth,
    retained: activeLastMonth - canceledThisMonth
  }
})

Revenue Forecasting

typescript
const revenueForecast = computed(() => {
  const currentMRR = subscriptionStats.value.mrr
  const growthRate = calculateMonthlyGrowthRate()
  
  const forecast = []
  for (let i = 1; i <= 12; i++) {
    const projectedMRR = currentMRR * Math.pow(1 + growthRate, i)
    forecast.push({
      month: new Date(Date.now() + i * 30 * 24 * 60 * 60 * 1000).toLocaleDateString('en-US', { month: 'short', year: 'numeric' }),
      mrr: projectedMRR,
      arr: projectedMRR * 12
    })
  }
  
  return forecast
})

Subscription Filtering

Advanced Filters

typescript
const filters = reactive({
  status: 'all',
  plan: 'all',
  dateRange: {
    start: null,
    end: null
  },
  paymentStatus: 'all',
  trialStatus: 'all',
  revenueRange: {
    min: null,
    max: null
  }
})

const filterOptions = {
  status: [
    { value: 'all', label: 'All Statuses' },
    { value: 'active', label: 'Active' },
    { value: 'trialing', label: 'Trial' },
    { value: 'past_due', label: 'Past Due' },
    { value: 'canceled', label: 'Canceled' }
  ],
  plan: [
    { value: 'all', label: 'All Plans' },
    { value: 'free', label: 'Free' },
    { value: 'pro', label: 'Pro' },
    { value: 'enterprise', label: 'Enterprise' }
  ]
}

Bulk Operations

Bulk Subscription Actions

typescript
const bulkActions = [
  {
    label: 'Export Selected',
    action: 'export',
    icon: 'download'
  },
  {
    label: 'Send Billing Reminder',
    action: 'billing_reminder',
    icon: 'mail'
  },
  {
    label: 'Apply Discount',
    action: 'discount',
    icon: 'percent'
  },
  {
    label: 'Cancel Subscriptions',
    action: 'cancel',
    icon: 'x',
    requiresConfirmation: true
  }
]

const executeBulkAction = async (action) => {
  if (selectedSubscriptions.value.length === 0) {
    toast.warning('No subscriptions selected')
    return
  }

  const result = await $fetch('/api/admin/subscriptions/bulk', {
    method: 'POST',
    body: { 
      subscriptionIds: selectedSubscriptions.value,
      action,
      adminId: user.value.id
    }
  })

  selectedSubscriptions.value = []
  refreshSubscriptions()
  toast.success(`Bulk ${action} completed successfully`)
}

Real-time Updates

Subscription Events

typescript
onMounted(() => {
  const { $socket } = useNuxtApp()
  
  $socket.on('subscription:created', (data) => {
    if (subscriptionsData.value?.subscriptions) {
      subscriptionsData.value.subscriptions.unshift(data)
      subscriptionsData.value.total++
    }
    toast.success(`New subscription: ${data.user.full_name}`)
  })
  
  $socket.on('subscription:canceled', (data) => {
    const subIndex = subscriptionsData.value?.subscriptions?.findIndex(s => s.id === data.id)
    if (subIndex !== -1) {
      subscriptionsData.value.subscriptions[subIndex].status = 'canceled'
      subscriptionsData.value.subscriptions[subIndex].canceled_at = data.canceled_at
    }
    toast.warning(`Subscription canceled: ${data.user.full_name}`)
  })
  
  $socket.on('payment:failed', (data) => {
    const subIndex = subscriptionsData.value?.subscriptions?.findIndex(s => s.id === data.subscriptionId)
    if (subIndex !== -1) {
      subscriptionsData.value.subscriptions[subIndex].status = 'past_due'
    }
    toast.error(`Payment failed: ${data.user.full_name}`)
  })
})

API Endpoints

Subscription Management APIs

GET  /api/admin/subscriptions              # List subscriptions with filtering
GET  /api/admin/subscriptions/:id          # Get subscription details
POST /api/subscription/cancel              # Cancel subscription
POST /api/subscription/resume              # Resume subscription
POST /api/subscription/change-plan         # Change subscription plan
POST /api/billing/retry-payment            # Retry failed payment
POST /api/billing/refund                   # Process refund
POST /api/admin/subscriptions/bulk         # Bulk operations
GET  /api/admin/revenue/analytics          # Revenue analytics

Export & Reporting

Subscription Export

typescript
const exportSubscriptions = async (format = 'csv') => {
  const exportData = {
    subscriptions: selectedSubscriptions.value.length > 0 ? selectedSubscriptions.value : 'all',
    format,
    fields: [
      'id', 'user_email', 'user_name', 'plan_name', 'status', 
      'current_period_start', 'current_period_end', 'amount'
    ],
    filters: filters
  }
  
  const response = await $fetch('/api/admin/subscriptions/export', {
    method: 'POST',
    body: exportData
  })
  
  downloadFile(response.data, `subscriptions-export-${new Date().toISOString().split('T')[0]}.${format}`)
  toast.success('Subscription data exported successfully')
}

The subscription management interface provides comprehensive tools for managing billing, revenue optimization, and subscription lifecycle operations while maintaining full integration with Stripe's billing system.

Built with love by mhdevfr