switched to paddle inline checkout, removed paypal and most of stripe. added product sync between app and paddle.
This commit is contained in:
@@ -3,7 +3,11 @@
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PackageResource\Pages;
|
||||
use App\Jobs\PullPackageFromPaddle;
|
||||
use App\Jobs\SyncPackageToPaddle;
|
||||
use App\Models\Package;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -11,21 +15,23 @@ use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Schemas\Components\Tabs as SchemaTabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab as SchemaTab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs as SchemaTabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab as SchemaTab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\BadgeColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Str;
|
||||
use UnitEnum;
|
||||
use BackedEnum;
|
||||
|
||||
class PackageResource extends Resource
|
||||
{
|
||||
@@ -150,6 +156,28 @@ class PackageResource extends Resource
|
||||
->columnSpanFull()
|
||||
->default([]),
|
||||
]),
|
||||
Section::make('Paddle Billing')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('paddle_product_id')
|
||||
->label('Paddle Produkt-ID')
|
||||
->maxLength(191)
|
||||
->helperText('Produkt aus Paddle Billing. Leer lassen, wenn noch nicht synchronisiert.')
|
||||
->placeholder('nicht verknüpft'),
|
||||
TextInput::make('paddle_price_id')
|
||||
->label('Paddle Preis-ID')
|
||||
->maxLength(191)
|
||||
->helperText('Preis-ID aus Paddle Billing, verknüpft mit diesem Paket.')
|
||||
->placeholder('nicht verknüpft'),
|
||||
Placeholder::make('paddle_sync_status')
|
||||
->label('Sync-Status')
|
||||
->content(fn (?Package $record) => $record?->paddle_sync_status ? Str::headline($record->paddle_sync_status) : '–')
|
||||
->columnSpanFull(),
|
||||
Placeholder::make('paddle_synced_at')
|
||||
->label('Zuletzt synchronisiert')
|
||||
->content(fn (?Package $record) => $record?->paddle_synced_at ? $record->paddle_synced_at->diffForHumans() : '–')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -214,6 +242,28 @@ class PackageResource extends Resource
|
||||
->label('Features')
|
||||
->wrap()
|
||||
->formatStateUsing(fn ($state) => static::formatFeaturesForDisplay($state)),
|
||||
TextColumn::make('paddle_product_id')
|
||||
->label('Paddle Produkt')
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->formatStateUsing(fn ($state) => $state ?: '-'),
|
||||
TextColumn::make('paddle_price_id')
|
||||
->label('Paddle Preis')
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->formatStateUsing(fn ($state) => $state ?: '-'),
|
||||
BadgeColumn::make('paddle_sync_status')
|
||||
->label('Sync-Status')
|
||||
->colors([
|
||||
'success' => 'synced',
|
||||
'warning' => 'syncing',
|
||||
'info' => 'dry-run',
|
||||
'danger' => ['failed', 'pull-failed'],
|
||||
])
|
||||
->formatStateUsing(fn ($state) => $state ? Str::headline($state) : null)
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('paddle_synced_at')
|
||||
->label('Sync am')
|
||||
->dateTime()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('type')
|
||||
@@ -224,6 +274,35 @@ class PackageResource extends Resource
|
||||
]),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('syncPaddle')
|
||||
->label('Mit Paddle abgleichen')
|
||||
->icon('heroicon-o-cloud-arrow-up')
|
||||
->color('success')
|
||||
->requiresConfirmation()
|
||||
->disabled(fn (Package $record) => $record->paddle_sync_status === 'syncing')
|
||||
->action(function (Package $record) {
|
||||
SyncPackageToPaddle::dispatch($record->id);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Paddle-Sync gestartet')
|
||||
->body('Das Paket wird im Hintergrund mit Paddle abgeglichen.')
|
||||
->send();
|
||||
}),
|
||||
Actions\Action::make('pullPaddle')
|
||||
->label('Status von Paddle holen')
|
||||
->icon('heroicon-o-cloud-arrow-down')
|
||||
->disabled(fn (Package $record) => ! $record->paddle_product_id && ! $record->paddle_price_id)
|
||||
->requiresConfirmation()
|
||||
->action(function (Package $record) {
|
||||
PullPackageFromPaddle::dispatch($record->id);
|
||||
|
||||
Notification::make()
|
||||
->info()
|
||||
->title('Paddle-Abgleich angefordert')
|
||||
->body('Der aktuelle Stand aus Paddle wird geladen und hier hinterlegt.')
|
||||
->send();
|
||||
}),
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PurchaseResource\Pages;
|
||||
use App\Models\PackagePurchase;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -11,23 +12,19 @@ use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\BadgeColumn;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use BackedEnum;
|
||||
use UnitEnum;
|
||||
|
||||
class PurchaseResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PackagePurchase::class;
|
||||
@@ -97,7 +94,7 @@ class PurchaseResource extends Resource
|
||||
->columns([
|
||||
BadgeColumn::make('type')
|
||||
->label('Type')
|
||||
->color(fn (string $state): string => match($state) {
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'endcustomer_event' => 'info',
|
||||
'reseller_subscription' => 'success',
|
||||
default => 'gray',
|
||||
@@ -164,11 +161,11 @@ class PurchaseResource extends Resource
|
||||
->color('danger')
|
||||
->icon('heroicon-o-arrow-uturn-left')
|
||||
->requiresConfirmation()
|
||||
->visible(fn (PackagePurchase $record): bool => !$record->refunded)
|
||||
->visible(fn (PackagePurchase $record): bool => ! $record->refunded)
|
||||
->action(function (PackagePurchase $record) {
|
||||
$record->update(['refunded' => true]);
|
||||
// TODO: Call Stripe/PayPal API for actual refund
|
||||
Log::info('Refund processed for purchase ID: ' . $record->id);
|
||||
// TODO: Call Stripe/Paddle API for actual refund
|
||||
Log::info('Refund processed for purchase ID: '.$record->id);
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
@@ -196,4 +193,4 @@ class PurchaseResource extends Resource
|
||||
// Add RelationManagers if needed
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ class ViewPurchase extends ViewRecord
|
||||
->color('danger')
|
||||
->icon('heroicon-o-arrow-uturn-left')
|
||||
->requiresConfirmation()
|
||||
->visible(fn ($record): bool => !$record->refunded)
|
||||
->visible(fn ($record): bool => ! $record->refunded)
|
||||
->action(function ($record) {
|
||||
$record->update(['refunded' => true]);
|
||||
// TODO: Call Stripe/PayPal API for actual refund
|
||||
// TODO: Call Stripe/Paddle API for actual refund
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,43 +3,42 @@
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TenantResource\Pages;
|
||||
use App\Models\Tenant;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use App\Filament\Resources\TenantResource\RelationManagers\PackagePurchasesRelationManager;
|
||||
use App\Filament\Resources\TenantResource\RelationManagers\TenantPackagesRelationManager;
|
||||
use Filament\Resources\RelationManagers\RelationGroup;
|
||||
use Filament\Notifications\Notification;
|
||||
use UnitEnum;
|
||||
use App\Models\Tenant;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use UnitEnum;
|
||||
|
||||
class TenantResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Tenant::class;
|
||||
|
||||
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-building-office';
|
||||
|
||||
protected static UnitEnum|string|null $navigationGroup = null;
|
||||
|
||||
public static function getNavigationGroup(): UnitEnum|string|null
|
||||
{
|
||||
return __('admin.nav.platform_management');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
public static function form(Schema $form): Schema
|
||||
{
|
||||
|
||||
|
||||
return $form->schema([
|
||||
TextInput::make('user.full_name')
|
||||
->label(__('admin.tenants.fields.name'))
|
||||
@@ -61,6 +60,11 @@ class TenantResource extends Resource
|
||||
->label(__('admin.tenants.fields.event_credits_balance'))
|
||||
->numeric()
|
||||
->readOnly(),
|
||||
TextInput::make('paddle_customer_id')
|
||||
->label('Paddle Customer ID')
|
||||
->maxLength(191)
|
||||
->helperText('Verknuepfung mit Paddle Billing Kundenkonto.')
|
||||
->nullable(),
|
||||
TextInput::make('total_revenue')
|
||||
->label(__('admin.tenants.fields.total_revenue'))
|
||||
->prefix('€')
|
||||
@@ -93,7 +97,7 @@ class TenantResource extends Resource
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
|
||||
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')->sortable(),
|
||||
@@ -104,6 +108,10 @@ class TenantResource extends Resource
|
||||
->getStateUsing(fn (Tenant $record) => $record->user?->full_name ?? 'Unbekannt'),
|
||||
Tables\Columns\TextColumn::make('slug')->searchable(),
|
||||
Tables\Columns\TextColumn::make('contact_email'),
|
||||
Tables\Columns\TextColumn::make('paddle_customer_id')
|
||||
->label('Paddle Customer')
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->formatStateUsing(fn ($state) => $state ?: '-'),
|
||||
Tables\Columns\TextColumn::make('event_credits_balance')
|
||||
->label(__('admin.tenants.fields.event_credits_balance'))
|
||||
->badge()
|
||||
@@ -162,6 +170,7 @@ class TenantResource extends Resource
|
||||
\App\Models\PackagePurchase::create([
|
||||
'tenant_id' => $record->id,
|
||||
'package_id' => $data['package_id'],
|
||||
'provider' => 'manual',
|
||||
'provider_id' => 'manual',
|
||||
'type' => 'reseller_subscription',
|
||||
'price' => 0,
|
||||
@@ -235,7 +244,7 @@ class TenantResource extends Resource
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
|
||||
|
||||
return [
|
||||
TenantPackagesRelationManager::class,
|
||||
PackagePurchasesRelationManager::class,
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class PackagePurchasesRelationManager extends RelationManager
|
||||
{
|
||||
@@ -43,24 +39,23 @@ class PackagePurchasesRelationManager extends RelationManager
|
||||
'reseller_subscription' => 'Reseller-Abo',
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('purchased_price')
|
||||
->label('Gekaufter Preis')
|
||||
->numeric()
|
||||
->step(0.01)
|
||||
->prefix('€')
|
||||
->required(),
|
||||
Select::make('provider_id')
|
||||
Select::make('provider')
|
||||
->label('Anbieter')
|
||||
->options([
|
||||
'paddle' => 'Paddle',
|
||||
'stripe' => 'Stripe',
|
||||
'paypal' => 'PayPal',
|
||||
'manual' => 'Manuell',
|
||||
'free' => 'Kostenlos',
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('transaction_id')
|
||||
->label('Transaktions-ID')
|
||||
TextInput::make('provider_id')
|
||||
->label('Provider-Referenz')
|
||||
->maxLength(255),
|
||||
TextInput::make('price')
|
||||
->label('Preis')
|
||||
->numeric()
|
||||
->step(0.01)
|
||||
->prefix('€'),
|
||||
Toggle::make('refunded')
|
||||
->label('Rückerstattet'),
|
||||
Textarea::make('metadata')
|
||||
@@ -82,7 +77,7 @@ class PackagePurchasesRelationManager extends RelationManager
|
||||
->color('success'),
|
||||
TextColumn::make('type')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match($state) {
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'endcustomer_event' => 'info',
|
||||
'reseller_subscription' => 'success',
|
||||
default => 'gray',
|
||||
@@ -90,15 +85,17 @@ class PackagePurchasesRelationManager extends RelationManager
|
||||
TextColumn::make('price')
|
||||
->money('EUR')
|
||||
->sortable(),
|
||||
TextColumn::make('provider_id')
|
||||
TextColumn::make('provider')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match($state) {
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'paddle' => 'success',
|
||||
'stripe' => 'info',
|
||||
'paypal' => 'warning',
|
||||
'manual' => 'gray',
|
||||
'free' => 'success',
|
||||
default => 'gray',
|
||||
}),
|
||||
TextColumn::make('transaction_id')
|
||||
TextColumn::make('provider_id')
|
||||
->label('Provider-Referenz')
|
||||
->copyable()
|
||||
->toggleable(),
|
||||
TextColumn::make('metadata')
|
||||
@@ -117,10 +114,10 @@ class PackagePurchasesRelationManager extends RelationManager
|
||||
'endcustomer_event' => 'Endkunden-Event',
|
||||
'reseller_subscription' => 'Reseller-Abo',
|
||||
]),
|
||||
SelectFilter::make('provider_id')
|
||||
SelectFilter::make('provider')
|
||||
->options([
|
||||
'paddle' => 'Paddle',
|
||||
'stripe' => 'Stripe',
|
||||
'paypal' => 'PayPal',
|
||||
'manual' => 'Manuell',
|
||||
'free' => 'Kostenlos',
|
||||
]),
|
||||
@@ -141,4 +138,3 @@ class PackagePurchasesRelationManager extends RelationManager
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,21 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class TenantPackagesRelationManager extends RelationManager
|
||||
{
|
||||
@@ -40,6 +37,11 @@ class TenantPackagesRelationManager extends RelationManager
|
||||
DateTimePicker::make('expires_at')
|
||||
->label('Ablaufdatum')
|
||||
->required(),
|
||||
TextInput::make('paddle_subscription_id')
|
||||
->label('Paddle Subscription ID')
|
||||
->maxLength(191)
|
||||
->helperText('Abonnement-ID aus Paddle Billing.')
|
||||
->nullable(),
|
||||
Toggle::make('active')
|
||||
->label('Aktiv'),
|
||||
Textarea::make('reason')
|
||||
@@ -70,6 +72,10 @@ class TenantPackagesRelationManager extends RelationManager
|
||||
TextColumn::make('expires_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
TextColumn::make('paddle_subscription_id')
|
||||
->label('Paddle Subscription')
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->formatStateUsing(fn ($state) => $state ?: '-'),
|
||||
IconColumn::make('active')
|
||||
->boolean()
|
||||
->color(fn (bool $state): string => $state ? 'success' : 'danger'),
|
||||
@@ -105,4 +111,4 @@ class TenantPackagesRelationManager extends RelationManager
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user