Filament: Stripe One-Time Payment Form with Elements

Filament is great for admin panels, but what if you want to use it as an e-shop with payments? In this tutorial, we will show how to integrate Stripe one-time checkout into Filament.

Filament Stripe Payment Element

Prepare for quite a long "ride" because there's a lot of work to implement it properly, with all the JS/PHP/Livewire elements, validation, and webhooks.


Table of Contents

  1. Data Preparation: Models/Migrations/Factories
  2. Filament Product Resource
  3. Custom Filament Pages: Checkout and Payment Status
  4. Back-end: Stripe PHP Library and Payment Intent
  5. Checkout Page: Stripe.js, Form and Stripe Elements
  6. Handle the Submit Event
  7. Show Payment Status
  8. Post-payment Events: Order Approval and Webhooks

Ready? Let's dive in!


1. Data Preparation

Our goals in this section:

  • Create Product and Order Model
  • Add relationships
  • Populate products table
  • Create a ProductResource list and view pages
  • Add the Buy product to view page

That is how our final pages should look like.

List Products

View Product

1.1. Migrations, Factories and Models

First, let's create Models; only the Product will have a factory.

php artisan make:model Product -mf
php artisan make:model Order -m

Then, update the migrations.

database/migrations/XXXXXX_create_products_table.php

Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->bigInteger('price');
$table->timestamps();
});

database/migrations/XXXXXX_create_orders_table.php

use App\Models\Product;
use App\Models\User;
 
// ...
 
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Product::class);
$table->foreignIdFor(User::class);
$table->bigInteger('amount');
$table->timestamps();
});

In ProductFactory, we define column data. Notice that we store the price in cents.

database/factories/ProductFactory.php

public function definition(): array
{
return [
'name' => fake()->words(3, asText: true),
'price' => rand(999, 9999),
];
}

app/Models/User.php

use Illuminate\Database\Eloquent\Relations\HasMany;
 
// ...
 
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}

And then add relationships into Models.

app/Models/Product.php

use Illuminate\Database\Eloquent\Relations\HasMany;
 
// ...
 
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}

app/Models/Order.php

use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
// ...
 
protected $fillable = [
'product_id',
'user_id',
'amount',
];
 
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
 
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

1.2. Database Seeder

Now let's update DatabaseSeeder.

database/seeders/DatabaseSeeder.php

use App\Models\Product;
use App\Models\User;
 
public function run(): void
{
User::factory()->create([
'name' => 'Admin',
'email' => 'admin@admin.com',
]);
 
Product::factory(100)->create();
}

And seed the database.

php artisan migrate:fresh --seed

2. Filament Product Resource

Generate ProductResource and view pages using the Artisan command. Filament doesn't create a view page by default, so we must add the --view flag when creating a resource.

php artisan make:filament-resource Product --view

Implementation of the resource file:

app/Filament/Resources/ProductResource.php

namespace App\Filament\Resources;
 
use App\Filament\Resources\ProductResource\Pages;
use App\Models\Product;
use Filament\Infolists\Components\Actions;
use Filament\Infolists\Components\Actions\Action;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Infolist;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use NumberFormatter;
 
class ProductResource extends Resource
{
protected static ?string $model = Product::class;
 
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
 
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
TextEntry::make('name'),
TextEntry::make('price')
->formatStateUsing(function ($state) {
$formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::CURRENCY);
 
return $formatter->formatCurrency($state / 100, 'eur');
}),
Actions::make([
Action::make('Buy product')
->url('/'),
]),
]);
}
 
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name'),
TextColumn::make('price')
->formatStateUsing(function ($state) {
$formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::CURRENCY);
 
return $formatter->formatCurrency($state / 100, 'eur');
}),
])
->actions([
Tables\Actions\ViewAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
 
public static function getPages(): array
{
return [
'index' => Pages\ListProducts::route('/'),
'view' => Pages\ViewProduct::route('/{record}'),
];
}
}

Filament's infolists can use...

The full tutorial [25 mins, 4879 words] is only for Premium Members

Login Or Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1056 lessons, total 42 h 44 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials