added cashier subscription, subscription plans

This commit is contained in:
Gitea
2025-05-03 06:13:19 +05:30
parent e04539f1b7
commit 6e2a750c4e
23 changed files with 1087 additions and 21 deletions

View File

@@ -0,0 +1,100 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\PlanResource\Pages;
use App\Filament\Resources\PlanResource\RelationManagers;
use App\Models\Plan;
use Filament\Forms;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Set;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\BooleanColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Str;
use phpDocumentor\Reflection\Types\Boolean;
class PlanResource extends Resource
{
protected static ?string $model = Plan::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Web Master';
public static function form(Form $form): Form
{
return $form
->schema([
Section::make('Plan Information')
->description('Add a new plan')
->schema([
TextInput::make('name')->label('Page Name')
->required(),
TextInput::make('description'),
TextInput::make('product_id')->required(),
TextInput::make('pricing_id')->required(),
TextInput::make('price')->numeric()->required(),
Select::make('monthly_billing')->options([
1 => 'Monthly',
0 => 'Yearly',
])->default(1)->required(),
KeyValue::make('details')
->label('Plan Details (Optional)')
->keyPlaceholder('Name')
->valuePlaceholder('Content')
->reorderable(),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')->label('Name'),
TextColumn::make('product_id')->label('Product'),
TextColumn::make('pricing_id')->label('Pricing'),
TextColumn::make('price')->label('Price'),
BooleanColumn::make('monthly_billing')->label('Monthly Billing'),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListPlans::route('/'),
'create' => Pages\CreatePlan::route('/create'),
'edit' => Pages\EditPlan::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Filament\Resources\PlanResource\Pages;
use App\Filament\Resources\PlanResource;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
class CreatePlan extends CreateRecord
{
protected static string $resource = PlanResource::class;
protected function getCreatedNotification(): ?Notification
{
return Notification::make()
->success()
->title('Plan created')
->body('Plan created successfully');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Filament\Resources\PlanResource\Pages;
use App\Filament\Resources\PlanResource;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
class EditPlan extends EditRecord
{
protected static string $resource = PlanResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
protected function getRedirectUrl(): ?string
{
return $this->getResource()::getUrl('index');
}
protected function getSavedNotification(): ?Notification
{
return Notification::make()
->success()
->title('Plan updated')
->body('Plan updated successfully');
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\PlanResource\Pages;
use App\Filament\Resources\PlanResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListPlans extends ListRecords
{
protected static string $resource = PlanResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Events\WebhookReceived;
use Livewire\Livewire;
class StripeEventListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['type'] === 'invoice.payment_succeeded') {
session()->flash('alert', ['type' => 'success', 'message' => 'Payment completed successfully.']);
Log::info('Payment succeeded');
}
if ($event->payload['type'] === 'customer.subscription.deleted') {
session()->flash('alert', ['type' => 'error', 'message' => 'Subscription canceled.']);
Log::info('Subscription canceled');
}
}
}

View File

@@ -2,12 +2,70 @@
namespace App\Livewire\Dashboard;
use Illuminate\Http\Request;
use Livewire\Component;
class Dashboard extends Component
{
public $message;
public $subscription;
public $plans;
public function paymentStatus(Request $request)
{
$status = $request->route('status');
$currentUrl = $request->fullUrl();
if ($status == 'success') {
return redirect()->route('dashboard')->with('status', 'success');
} elseif ($status == 'cancel') {
return redirect()->route('dashboard')->with('status', 'cancel');
}
}
public function mount(Request $request)
{
try {
$status = $request->session()->get('status');
if (isset($status)) {
if ($status == 'success') {
$this->message = ['type' => 'success', 'message' => 'Order completed successfully.'];
} else {
$this->message = ['type' => 'error', 'message' => 'Order cancelled.'];
}
$request->session()->forget('status');
}
} catch (\Exception $exception) {
}
if (auth()->user()->subscribedToProduct(config('app.plans')[0]['product_id'])) {
try {
$result = auth()->user()->subscriptions()->where(['stripe_status' => 'active'])->orderByDesc('updated_at')->first();
$userPriceID = $result['items'][0]['stripe_price'];
$subscriptionEnd = $result['ends_at'];
$planName = null; // Default value if not found
foreach (config('app.plans') as $plan) {
if ($plan['pricing_id'] === $userPriceID) {
$planName = $plan['name'];
break;
}
}
$this->subscription['name'] = $planName;
$this->subscription['ends_at'] = $subscriptionEnd;
} catch (\Exception $e) {
\Log::error($e->getMessage());
}
}
}
public function render()
{
return view('livewire.dashboard.dashboard')->layout('components.layouts.dashboard');
return view('livewire.dashboard.dashboard')->layout('components.layouts.dashboard')->with('message', $this->message);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Livewire\Dashboard;
use Livewire\Component;
class Pricing extends Component
{
public $plans;
public function mount(): void
{
$this->plans = config('app.plans');
}
public function choosePlan($pricing_id): void
{
$this->redirect(route('checkout', $pricing_id));
}
public function render()
{
return view('livewire.dashboard.pricing');
}
}

23
app/Models/Plan.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Plan extends Model
{
protected $fillable = [
'name',
'description',
'product_id',
'pricing_id',
'price',
'monthly_billing',
'details'
];
protected $casts = [
'details' => 'json',
'monthly_billing' => 'boolean',
];
}

View File

@@ -10,11 +10,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Str;
use Laravel\Cashier\Billable;
class User extends Authenticatable implements FilamentUser, MustVerifyEmail
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasFactory, Notifiable, Billable;
/**
* The attributes that are mass assignable.

View File

@@ -4,8 +4,10 @@ namespace App\Providers;
use App\Models\Blog;
use App\Models\Menu;
use App\Models\Plan;
use DB;
use Illuminate\Support\ServiceProvider;
use Laravel\Cashier\Cashier;
class AppServiceProvider extends ServiceProvider
{
@@ -33,8 +35,15 @@ class AppServiceProvider extends ServiceProvider
return Blog::where('is_published', 1)->get();
});
$plans = cache()->remember('app_plans', now()->addHours(6), function () {
return Plan::all();
});
config(['app.settings' => (array) $settings]);
config(['app.menus' => $menus]);
config(['app.blogs' => $blogs]);
config(['app.plans' => $plans]);
Cashier::calculateTaxes();
}
}