diff --git a/composer.json b/composer.json index b6c63bf..6e1faab 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,8 @@ { "require": { "predis/predis": "^3.0", - "whilesmart/entitlements-cashier": "^0.2.1" + "whilesmart/eloquent-admin": "^0.3", + "whilesmart/eloquent-entitlements": "^2.1", + "whilesmart/entitlements-cashier": "^0.4" } } diff --git a/src/CloudServiceProvider.php b/src/CloudServiceProvider.php index 624d731..b13f494 100644 --- a/src/CloudServiceProvider.php +++ b/src/CloudServiceProvider.php @@ -5,8 +5,11 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use Trakli\Cloud\Console\SyncPlansCommand; +use Trakli\Cloud\Engagement\BillingMetricProvider; use Trakli\Cloud\Support\FreePlanFallbackSource; use Trakli\Cloud\Support\TokenMeterUsage; +use Whilesmart\Admin\Offers\FreePasses; +use Whilesmart\Admin\Offers\StripePromotionCodes; use Whilesmart\Entitlements\Contracts\Entitlements; use Whilesmart\Entitlements\Contracts\PlanSource; use Whilesmart\Entitlements\Contracts\UsageMeter; @@ -21,7 +24,7 @@ class CloudServiceProvider extends ServiceProvider */ public function register(): void { - $this->mergeConfigFrom(__DIR__.'/../config/cloudplans.php', 'cloudplans'); + $this->mergeConfigFrom(__DIR__ . '/../config/cloudplans.php', 'cloudplans'); } /** @@ -33,6 +36,14 @@ public function boot(): void $this->registerRoutes(); $this->publishConfig(); $this->enforcePlans(); + config(['engagement.providers' => array_values(array_unique([ + BillingMetricProvider::class, + ...(array) config('engagement.providers', []), + ]))]); + config(['admin.offer_providers' => array_values(array_unique(array_merge( + (array) config('admin.offer_providers', []), + [StripePromotionCodes::class, FreePasses::class], + )))]); app(\Illuminate\Routing\Router::class)->aliasMiddleware('feature', \App\Http\Middleware\RequireFeature::class); if (config('entitlements-cashier.trial_days') === null) { diff --git a/src/Engagement/BillingMetricProvider.php b/src/Engagement/BillingMetricProvider.php new file mode 100644 index 0000000..e300318 --- /dev/null +++ b/src/Engagement/BillingMetricProvider.php @@ -0,0 +1,81 @@ +getMorphClass(); + $latest = $model::query()->selectRaw('MAX(id)')->where('owner_type', $ownerType)->groupBy('owner_id'); + $subscriptions = $model::query()->with('plan')->whereIn('id', $latest)->active() + ->whereHasMorph('owner', [User::class])->get(); + $paying = $subscriptions->where('status', SubscriptionStatus::Active); + $trialing = $subscriptions->where('status', SubscriptionStatus::Trialing); + $plans = $subscriptions->groupBy(fn ($subscription) => $subscription->plan?->name ?? 'Complimentary'); + + return [ + Metric::count('active', 'Paying', $paying->count()), + Metric::count('trialing', 'On trial', $trialing->count()), + Metric::count( + 'comped', + 'Complimentary users', + $subscriptions->where('status', SubscriptionStatus::Comped)->count(), + ), + Metric::count('trials_ending', 'Trials ending in this period', $trialing->filter( + fn ($subscription) => $subscription->current_period_end?->between($period->start, $period->end), + )->count()), + ...$this->monthlyValues($paying, 'monthly_plan_value', 'Monthly plan value'), + ...$this->monthlyValues($trialing, 'monthly_trial_value', 'Monthly trial plan value'), + Metric::ranking('by_plan', 'Current plans', $plans->map( + fn (Collection $subscribers, string $name) => ['label' => $name, 'value' => $subscribers->count()], + )->sortByDesc('value')->values()->all()), + ]; + } + + private function monthlyValues(Collection $subscriptions, string $key, string $label): array + { + $totals = []; + foreach ($subscriptions as $subscription) { + $price = $subscription->plan?->metadata['price'] ?? null; + if ($price === null) { + continue; + } + + $currency = strtoupper($price['currency']); + $amount = $price['amount_cents'] / (($price['interval'] === 'year') ? 12 : 1); + $totals[$currency] = ($totals[$currency] ?? 0) + $amount; + } + + ksort($totals); + + return array_map( + fn (string $currency) => Metric::sum( + sprintf('%s_%s', $key, strtolower($currency)), + $label, + round($totals[$currency] / 100, 2), + $currency, + ), + array_keys($totals), + ); + } +}