feat: implement comprehensive multi-provider payment processing system
- Add unified payment provider architecture with contract-based design - Implement 6 payment providers: Stripe, Lemon Squeezy, Polar, Oxapay, Crypto, Activation Keys - Create subscription management with lifecycle handling (create, cancel, pause, resume, update) - Add coupon system with usage tracking and trial extensions - Build Filament admin resources for payment providers, subscriptions, coupons, and trials - Implement payment orchestration service with provider registry and configuration management - Add comprehensive payment logging and webhook handling for all providers - Create customer analytics dashboard with revenue, churn, and lifetime value metrics - Add subscription migration service for provider switching - Include extensive test coverage for all payment functionality
This commit is contained in:
120
app/Filament/Widgets/CustomerAnalyticsOverview.php
Normal file
120
app/Filament/Widgets/CustomerAnalyticsOverview.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Subscription;
|
||||
use App\Models\User;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
|
||||
class CustomerAnalyticsOverview extends BaseWidget
|
||||
{
|
||||
protected function getStats(): array
|
||||
{
|
||||
$totalCustomers = User::count();
|
||||
$payingCustomers = Subscription::active()->distinct('user_id')->count('user_id');
|
||||
$trialCustomers = Subscription::onTrial()->distinct('user_id')->count('user_id');
|
||||
$churnedCustomers = Subscription::where('status', 'cancelled')->distinct('user_id')->count('user_id');
|
||||
|
||||
$mrr = $this->calculateMRR();
|
||||
$arr = $mrr * 12;
|
||||
$arpu = $payingCustomers > 0 ? $mrr / $payingCustomers : 0;
|
||||
$ltv = $arpu * 12; // Simplified LTV calculation
|
||||
|
||||
return [
|
||||
Stat::make('Total Customers', $totalCustomers)
|
||||
->description('All registered users')
|
||||
->descriptionIcon('heroicon-o-users')
|
||||
->color('primary'),
|
||||
|
||||
Stat::make('Paying Customers', $payingCustomers)
|
||||
->description($this->getCustomerGrowthRate($payingCustomers))
|
||||
->descriptionIcon($this->getGrowthIcon($payingCustomers))
|
||||
->color($this->getGrowthColor($payingCustomers)),
|
||||
|
||||
Stat::make('Trial Customers', $trialCustomers)
|
||||
->description('Currently on trial')
|
||||
->descriptionIcon('heroicon-o-clock')
|
||||
->color('warning'),
|
||||
|
||||
Stat::make('Churned Customers', $churnedCustomers)
|
||||
->description($this->getChurnRate($churnedCustomers, $payingCustomers + $churnedCustomers))
|
||||
->descriptionIcon('heroicon-o-arrow-trending-down')
|
||||
->color('danger'),
|
||||
|
||||
Stat::make('Monthly Recurring Revenue', '$'.number_format($mrr, 2))
|
||||
->description('MRR from active subscriptions')
|
||||
->descriptionIcon('heroicon-o-currency-dollar')
|
||||
->color('success'),
|
||||
|
||||
Stat::make('Annual Recurring Revenue', '$'.number_format($arr, 2))
|
||||
->description('ARR projection')
|
||||
->descriptionIcon('heroicon-o-chart-bar')
|
||||
->color('success'),
|
||||
|
||||
Stat::make('Average Revenue Per User', '$'.number_format($arpu, 2))
|
||||
->description('ARPU for paying customers')
|
||||
->descriptionIcon('heroicon-o-calculator')
|
||||
->color('info'),
|
||||
|
||||
Stat::make('Lifetime Value', '$'.number_format($ltv, 2))
|
||||
->description('Estimated customer LTV')
|
||||
->descriptionIcon('heroicon-o-gift')
|
||||
->color('purple'),
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateMRR(): float
|
||||
{
|
||||
return Subscription::active()
|
||||
->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
||||
->sum('plans.price');
|
||||
}
|
||||
|
||||
private function getCustomerGrowthRate(int $current): string
|
||||
{
|
||||
$previous = Subscription::active()
|
||||
->where('created_at', '<', now()->subMonth())
|
||||
->distinct('user_id')
|
||||
->count('user_id');
|
||||
|
||||
if ($previous == 0) {
|
||||
return 'New customers';
|
||||
}
|
||||
|
||||
$growth = (($current - $previous) / $previous) * 100;
|
||||
|
||||
return $growth >= 0 ? "+{$growth}%" : "{$growth}%";
|
||||
}
|
||||
|
||||
private function getGrowthIcon(int $current): string
|
||||
{
|
||||
$previous = Subscription::active()
|
||||
->where('created_at', '<', now()->subMonth())
|
||||
->distinct('user_id')
|
||||
->count('user_id');
|
||||
|
||||
return $current > $previous ? 'heroicon-o-arrow-trending-up' : 'heroicon-o-arrow-trending-down';
|
||||
}
|
||||
|
||||
private function getGrowthColor(int $current): string
|
||||
{
|
||||
$previous = Subscription::active()
|
||||
->where('created_at', '<', now()->subMonth())
|
||||
->distinct('user_id')
|
||||
->count('user_id');
|
||||
|
||||
return $current > $previous ? 'success' : 'danger';
|
||||
}
|
||||
|
||||
private function getChurnRate(int $churned, int $total): string
|
||||
{
|
||||
if ($total == 0) {
|
||||
return '0% churn rate';
|
||||
}
|
||||
|
||||
$rate = ($churned / $total) * 100;
|
||||
|
||||
return "{$rate}% churn rate";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user