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:
idevakk
2025-11-19 09:37:00 -08:00
parent 0560016f33
commit 27ac13948c
83 changed files with 15613 additions and 103 deletions

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Filament\Widgets;
use App\Models\Coupon;
use App\Models\CouponUsage;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use Illuminate\Support\Facades\DB;
class CouponPerformanceMetrics extends BaseWidget
{
protected static ?int $sort = 3;
protected function getStats(): array
{
$totalCoupons = Coupon::count();
$activeCoupons = Coupon::where('is_active', true)->count();
$totalUsages = CouponUsage::count();
$totalDiscount = CouponUsage::sum('discount_amount');
$conversionRate = $this->getCouponConversionRate();
$avgDiscountValue = $totalUsages > 0 ? $totalDiscount / $totalUsages : 0;
$topPerformingCoupon = $this->getTopPerformingCoupon();
$monthlyUsage = $this->getMonthlyUsage();
return [
Stat::make('Total Coupons', $totalCoupons)
->description($activeCoupons.' active')
->descriptionIcon('heroicon-o-ticket')
->color('primary'),
Stat::make('Total Usages', $totalUsages)
->description($this->getUsageGrowthRate())
->descriptionIcon($this->getUsageGrowthIcon())
->color($this->getUsageGrowthColor()),
Stat::make('Total Discount Given', '$'.number_format($totalDiscount, 2))
->description('Total value of discounts')
->descriptionIcon('heroicon-o-gift')
->color('success'),
Stat::make('Conversion Rate', $conversionRate.'%')
->description('Coupon to subscription rate')
->descriptionIcon('heroicon-o-chart-bar')
->color('info'),
Stat::make('Avg Discount Value', '$'.number_format($avgDiscountValue, 2))
->description('Per usage average')
->descriptionIcon('heroicon-o-calculator')
->color('warning'),
Stat::make('Top Performing', $topPerformingCoupon ? ($topPerformingCoupon['name'] ?? 'N/A') : 'N/A')
->description($topPerformingCoupon ? ($topPerformingCoupon['usages'] ?? 0).' usages' : '0 usages')
->descriptionIcon('heroicon-o-trophy')
->color('purple'),
Stat::make('Monthly Usage', $monthlyUsage)
->description('This month')
->descriptionIcon('heroicon-o-calendar')
->color('success'),
Stat::make('Revenue Impact', '$'.number_format($this->calculateRevenueImpact(), 2))
->description('Estimated new revenue')
->descriptionIcon('heroicon-o-currency-dollar')
->color('success'),
];
}
private function getCouponConversionRate(): string
{
$totalCoupons = Coupon::count();
if ($totalCoupons == 0) {
return '0';
}
$usedCoupons = Coupon::whereHas('usages')->count();
return number_format(($usedCoupons / $totalCoupons) * 100, 1);
}
private function getUsageGrowthRate(): string
{
$currentMonth = CouponUsage::whereMonth('created_at', now()->month)->count();
$previousMonth = CouponUsage::whereMonth('created_at', now()->subMonth()->month)->count();
if ($previousMonth == 0) {
return 'New this month';
}
$growth = (($currentMonth - $previousMonth) / $previousMonth) * 100;
return $growth >= 0 ? "+{$growth}%" : "{$growth}%";
}
private function getUsageGrowthIcon(): string
{
$currentMonth = CouponUsage::whereMonth('created_at', now()->month)->count();
$previousMonth = CouponUsage::whereMonth('created_at', now()->subMonth()->month)->count();
return $currentMonth > $previousMonth ? 'heroicon-o-arrow-trending-up' : 'heroicon-o-arrow-trending-down';
}
private function getUsageGrowthColor(): string
{
$currentMonth = CouponUsage::whereMonth('created_at', now()->month)->count();
$previousMonth = CouponUsage::whereMonth('created_at', now()->subMonth()->month)->count();
return $currentMonth > $previousMonth ? 'success' : 'danger';
}
private function getTopPerformingCoupon(): ?array
{
return CouponUsage::query()
->select('coupon_id', DB::raw('count(*) as usages, sum(discount_amount) as total_discount'))
->with('coupon:id,code')
->groupBy('coupon_id')
->orderBy('usages', 'desc')
->first()
?->toArray();
}
private function getMonthlyUsage(): int
{
return CouponUsage::whereMonth('created_at', now()->month)->count();
}
private function calculateRevenueImpact(): float
{
// Estimate revenue from coupons that led to subscriptions
return CouponUsage::query()
->whereHas('subscription', function ($query) {
$query->where('status', 'active');
})
->join('subscriptions', 'coupon_usages.subscription_id', '=', 'subscriptions.id')
->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
->sum('plans.price');
}
}