Skip to content

Commit 19d5ae8

Browse files
simonhampclaude
andauthored
Add price_paid column, billing interval, subscriber income chart, and user growth chart (#324)
- Add price_paid column to subscriptions table with migration - Backfill command to populate price_paid from Stripe invoices - Show price_paid and billing interval on Subscription admin screens - Replace LicensesChart with SubscriberIncomeChart on dashboard - Improve UsersChart with subscriber vs non-subscriber breakdown - Store price_paid in HandleInvoicePaidJob for new subscriptions Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 416bee4 commit 19d5ae8

13 files changed

Lines changed: 751 additions & 1444 deletions
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Sleep;
7+
use Laravel\Cashier\Cashier;
8+
use Laravel\Cashier\Subscription;
9+
10+
class BackfillSubscriptionPrices extends Command
11+
{
12+
protected $signature = 'subscriptions:backfill-prices';
13+
14+
protected $description = 'Backfill price_paid on subscriptions from Stripe invoices';
15+
16+
public function handle(): int
17+
{
18+
$subscriptions = Subscription::whereNull('price_paid')->get();
19+
20+
if ($subscriptions->isEmpty()) {
21+
$this->info('No subscriptions need backfilling.');
22+
23+
return self::SUCCESS;
24+
}
25+
26+
$this->info("Backfilling {$subscriptions->count()} subscriptions...");
27+
28+
$bar = $this->output->createProgressBar($subscriptions->count());
29+
$bar->start();
30+
31+
$errors = 0;
32+
33+
foreach ($subscriptions as $subscription) {
34+
try {
35+
$invoices = Cashier::stripe()->invoices->all([
36+
'subscription' => $subscription->stripe_id,
37+
'limit' => 1,
38+
]);
39+
40+
if (! empty($invoices->data)) {
41+
$subscription->update([
42+
'price_paid' => max(0, $invoices->data[0]->total),
43+
]);
44+
}
45+
} catch (\Exception $e) {
46+
$errors++;
47+
$this->newLine();
48+
$this->error("Failed for subscription {$subscription->stripe_id}: {$e->getMessage()}");
49+
}
50+
51+
$bar->advance();
52+
Sleep::usleep(100_000);
53+
}
54+
55+
$bar->finish();
56+
$this->newLine(2);
57+
58+
if ($errors > 0) {
59+
$this->warn("{$errors} subscription(s) failed to backfill.");
60+
}
61+
62+
$this->info('Backfill complete.');
63+
64+
return self::SUCCESS;
65+
}
66+
}

app/Filament/Pages/Dashboard.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
namespace App\Filament\Pages;
44

55
use App\Filament\Widgets\LicenseDistributionChart;
6-
use App\Filament\Widgets\LicensesChart;
76
use App\Filament\Widgets\PluginRevenueChart;
87
use App\Filament\Widgets\StatsOverview;
8+
use App\Filament\Widgets\SubscriberIncomeChart;
99
use App\Filament\Widgets\UsersChart;
1010
use Filament\Pages\Dashboard as BaseDashboard;
1111

@@ -24,7 +24,7 @@ public function getWidgets(): array
2424
{
2525
return [
2626
UsersChart::class,
27-
LicensesChart::class,
27+
SubscriberIncomeChart::class,
2828
LicenseDistributionChart::class,
2929
PluginRevenueChart::class,
3030
];

app/Filament/Resources/SubscriptionResource.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ public static function table(Table $table): Table
9292
})
9393
->searchable()
9494
->sortable(),
95+
Tables\Columns\TextColumn::make('billing_interval')
96+
->label('Interval')
97+
->state(fn (Subscription $record): string => $record->stripe_price === config('subscriptions.plans.max.stripe_price_id_monthly')
98+
? 'Monthly'
99+
: 'Annual'
100+
)
101+
->badge(),
102+
Tables\Columns\TextColumn::make('price_paid')
103+
->label('Price Paid')
104+
->money('usd', divideBy: 100)
105+
->sortable(),
95106
// Tables\Columns\TextColumn::make('trial_ends_at')
96107
// ->dateTime()
97108
// ->sortable(),

app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ public function infolist(Schema $schema): Schema
6464
}
6565
}),
6666
Components\TextEntry::make('quantity'),
67+
Components\TextEntry::make('billing_interval')
68+
->label('Billing Interval')
69+
->state(fn ($record): string => $record->stripe_price === config('subscriptions.plans.max.stripe_price_id_monthly')
70+
? 'Monthly'
71+
: 'Annual'
72+
)
73+
->badge(),
74+
Components\TextEntry::make('price_paid')
75+
->label('Price Paid')
76+
->money('usd', divideBy: 100),
6777
Components\TextEntry::make('trial_ends_at')
6878
->dateTime(),
6979
Components\TextEntry::make('ends_at')

app/Filament/Widgets/LicensesChart.php

Lines changed: 0 additions & 167 deletions
This file was deleted.

0 commit comments

Comments
 (0)