Filament: Stripe One-Time Payment Form with Elements

Tutorial last revisioned on July 02, 2024 with Filament v3

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:
  • 60 courses (1091 lessons, total 42 h 44 min)
  • 80 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials