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