# Payment System Integration Guide
## Unified Payment Transactions for Reservations & Bookings
## Overview
The system now uses a **unified `payment_transactions` table** for both:
- ✅ **Reservations** (existing functionality)
- ✅ **Bookings** (new functionality with deposit/check-in/check-out flow)
## Database Structure
### Payment Transactions Table
Single table handling all payment types:
```sql
payment_transactions
├── id
├── reservation_id (nullable) - For reservation payments
├── booking_id (nullable) - For booking payments
├── user_id - Staff who processed payment
├── client_id - Customer who made payment
├── amount
├── currency
├── payment_method (cash, card_online, card_inplace, bank_transfer, paypal, stripe, mypos)
├── payment_gateway (stripe, paypal, mypos, etc.)
├── payment_stage (deposit, check_in, check_out, full, partial, refund)
├── transaction_type (deposit, withdrawal, full_transfer)
├── status (pending, completed, failed, refunded, canceled)
├── transaction_id - Unique transaction identifier
├── reference_number - Gateway reference
├── receipt_number
├── fiscal_receipt_number
├── notes
├── metadata (JSON)
├── paid_at
├── company_id
├── workspace_id
└── timestamps
```
## Admin Panel Integration
### 1. Booking Resource - Payment Tab
**File:** `app/Filament/Resources/BookingResource.php`
Add a "Payments" relation manager to show all payments for a booking:
```php
use App\Filament\Resources\BookingResource\RelationManagers\PaymentsRelationManager;
public static function getRelations(): array
{
return [
PaymentsRelationManager::class,
];
}
```
**File:** `app/Filament/Resources/BookingResource/RelationManagers/PaymentsRelationManager.php`
```php
<?php
namespace App\Filament\Resources\BookingResource\RelationManagers;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
class PaymentsRelationManager extends RelationManager
{
protected static string $relationship = 'payments';
protected static ?string $title = 'Payment History';
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('paid_at')
->label('Date')
->dateTime('d M Y H:i')
->sortable(),
BadgeColumn::make('payment_stage_label')
->label('Stage')
->colors([
'primary' => 'deposit',
'success' => 'check_in',
'warning' => 'check_out',
'info' => 'full',
]),
TextColumn::make('amount')
->money('EUR')
->sortable(),
TextColumn::make('payment_method_label')
->label('Method'),
BadgeColumn::make('status_label')
->label('Status')
->colors([
'success' => 'completed',
'warning' => 'pending',
'danger' => 'failed',
'gray' => 'canceled',
]),
TextColumn::make('transaction_id')
->label('Transaction ID')
->limit(20)
->copyable(),
TextColumn::make('user.name')
->label('Processed By')
->default('System'),
TextColumn::make('notes')
->limit(30)
->tooltip(fn ($record) => $record->notes),
])
->defaultSort('paid_at', 'desc')
->headerActions([
// Add manual payment action
])
->actions([
EditAction::make(),
DeleteAction::make(),
]);
}
}
```
### 2. Booking List - Payment Status Column
**File:** `app/Filament/Resources/BookingResource/Tables/BookingsTable.php`
```php
use Filament\Tables\Columns\BadgeColumn;
use Filament\Tables\Columns\TextColumn;
public static function configure(Table $table): Table
{
return $table->columns([
// ... existing columns
TextColumn::make('total_amount')
->label('Total')
->money('EUR')
->sortable(),
TextColumn::make('paid_amount')
->label('Paid')
->money('EUR')
->sortable(),
TextColumn::make('remaining_amount')
->label('Remaining')
->money('EUR')
->sortable()
->color(fn ($record) => $record->remaining_amount > 0 ? 'warning' : 'success'),
BadgeColumn::make('payment_status')
->label('Payment')
->colors([
'danger' => 'unpaid',
'warning' => 'partially_paid',
'info' => 'deposit_paid',
'success' => 'paid',
])
->icons([
'heroicon-o-x-circle' => 'unpaid',
'heroicon-o-clock' => 'partially_paid',
'heroicon-o-check-circle' => 'deposit_paid',
'heroicon-o-check-badge' => 'paid',
]),
]);
}
```
### 3. Booking View - Payment Summary Widget
**File:** `app/Filament/Resources/BookingResource/Widgets/PaymentSummaryWidget.php`
```php
<?php
namespace App\Filament\Resources\BookingResource\Widgets;
use App\Models\Booking;
use App\Services\BookingPaymentService;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class PaymentSummaryWidget extends StatsOverviewWidget
{
public ?Booking $record = null;
protected function getStats(): array
{
$paymentService = new BookingPaymentService();
$summary = $paymentService->getPaymentSummary($this->record);
return [
Stat::make('Total Amount', '€' . number_format($summary['total'], 2))
->description('Including tax')
->descriptionIcon('heroicon-o-currency-euro')
->color('primary'),
Stat::make('Deposit Required', '€' . number_format($summary['deposit_required'], 2))
->description($summary['deposit_paid'] ? 'Paid ✓' : 'Pending')
->descriptionIcon($summary['deposit_paid'] ? 'heroicon-o-check-circle' : 'heroicon-o-clock')
->color($summary['deposit_paid'] ? 'success' : 'warning'),
Stat::make('Total Paid', '€' . number_format($summary['total_paid'], 2))
->description('Across all payments')
->descriptionIcon('heroicon-o-banknotes')
->color('success'),
Stat::make('Remaining Balance', '€' . number_format($summary['remaining_balance'], 2))
->description($summary['fully_paid'] ? 'Fully paid' : 'Due')
->descriptionIcon($summary['fully_paid'] ? 'heroicon-o-check-badge' : 'heroicon-o-exclamation-triangle')
->color($summary['fully_paid'] ? 'success' : 'danger'),
];
}
}
```
### 4. Check-In Action with Payment
**File:** `app/Filament/Resources/BookingResource/Pages/CheckInBooking.php`
```php
<?php
namespace App\Filament\Resources\BookingResource\Pages;
use App\Filament\Resources\BookingResource;
use App\Services\BookingPaymentService;
use Filament\Resources\Pages\Page;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
class CheckInBooking extends Page
{
protected static string $resource = BookingResource::class;
protected static string $view = 'filament.resources.booking-resource.pages.check-in-booking';
public ?array $data = [];
public $amountDue = 0;
public function mount(): void
{
$paymentService = new BookingPaymentService();
$this->amountDue = $paymentService->calculateCheckInAmount($this->record);
$this->form->fill([
'amount' => $this->amountDue,
'payment_method' => 'cash',
]);
}
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('amount')
->label('Amount Due')
->prefix('€')
->numeric()
->disabled()
->dehydrated(),
Select::make('payment_method')
->label('Payment Method')
->options([
'cash' => 'Cash',
'card_inplace' => 'Card (Terminal)',
'bank_transfer' => 'Bank Transfer',
])
->required()
->visible(fn () => $this->amountDue > 0),
Textarea::make('notes')
->label('Notes')
->rows(3),
])
->statePath('data');
}
public function checkIn(): void
{
$data = $this->form->getState();
$paymentService = new BookingPaymentService();
// Process payment if amount due
if ($this->amountDue > 0) {
$paymentService->processCheckInPayment(
$this->record,
$this->amountDue,
$data['payment_method']
);
}
// Update booking status
$this->record->update([
'status' => 'checked_in',
'checked_in_at' => now(),
]);
Notification::make()
->title('Check-in Successful')
->body('Booking checked in successfully. Payment of €' . number_format($this->amountDue, 2) . ' received.')
->success()
->send();
return redirect()->route('filament.admin.resources.bookings.view', $this->record);
}
}
```
### 5. Check-Out Action with Payment
**File:** `app/Filament/Resources/BookingResource/Pages/CheckOutBooking.php`
```php
<?php
namespace App\Filament\Resources\BookingResource\Pages;
use App\Filament\Resources\BookingResource;
use App\Services\BookingPaymentService;
use Filament\Resources\Pages\Page;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
class CheckOutBooking extends Page
{
protected static string $resource = BookingResource::class;
protected static string $view = 'filament.resources.booking-resource.pages.check-out-booking';
public ?array $data = [];
public $additionalCharges = 0;
public function mount(): void
{
$paymentService = new BookingPaymentService();
$this->additionalCharges = $paymentService->calculateCheckOutAmount($this->record);
$this->form->fill([
'additional_amount' => $this->additionalCharges,
'payment_method' => 'cash',
]);
}
public function form(Form $form): Form
{
return $form
->schema([
Repeater::make('additional_items')
->label('Additional Charges')
->schema([
TextInput::make('description')
->required(),
TextInput::make('amount')
->numeric()
->prefix('€')
->required(),
])
->columns(2)
->addActionLabel('Add Charge'),
TextInput::make('additional_amount')
->label('Total Additional Charges')
->prefix('€')
->numeric()
->disabled(),
Select::make('payment_method')
->label('Payment Method')
->options([
'cash' => 'Cash',
'card_inplace' => 'Card (Terminal)',
])
->required()
->visible(fn () => $this->additionalCharges > 0),
])
->statePath('data');
}
public function checkOut(): void
{
$data = $this->form->getState();
$paymentService = new BookingPaymentService();
// Process payment for additional charges
if ($this->additionalCharges > 0) {
$paymentService->processCheckOutPayment(
$this->record,
$this->additionalCharges,
$data['payment_method']
);
}
// Update booking status
$this->record->update([
'status' => 'completed',
'checked_out_at' => now(),
]);
Notification::make()
->title('Check-out Successful')
->body('Booking completed. Payment of €' . number_format($this->additionalCharges, 2) . ' received.')
->success()
->send();
return redirect()->route('filament.admin.resources.bookings.view', $this->record);
}
}
```
## Client UI Integration
### 1. Booking Creation with Deposit Payment
**File:** `app/Filament/Client/Pages/CreateBooking.php`
```php
protected function afterCreate(): void
{
$paymentService = new BookingPaymentService();
// Calculate totals
$totals = $paymentService->calculateBookingTotal($this->record);
$depositAmount = $paymentService->calculateDepositAmount($this->record);
// Update booking with calculated amounts
$this->record->update([
'total_amount' => $totals['total'],
'subtotal_amount' => $totals['subtotal'],
'tax_amount' => $totals['tax_amount'],
'deposit_amount' => $depositAmount,
'remaining_amount' => $totals['total'] - $depositAmount,
]);
// If deposit required, redirect to payment
if ($depositAmount > 0 && financial_setting('require_deposit_for_booking')) {
session()->flash('booking_id', $this->record->id);
return redirect()->route('client.booking.payment', $this->record);
}
}
```
### 2. Client Payment Page
**File:** `app/Filament/Client/Pages/BookingPayment.php`
```php
<?php
namespace App\Filament\Client\Pages;
use App\Models\Booking;
use App\Services\BookingPaymentService;
use Filament\Pages\Page;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Form;
class BookingPayment extends Page
{
protected static string $view = 'filament.client.pages.booking-payment';
protected static bool $shouldRegisterNavigation = false;
public Booking $booking;
public ?array $data = [];
public $depositAmount = 0;
public $paymentSummary = [];
public function mount(Booking $booking): void
{
$this->booking = $booking;
$paymentService = new BookingPaymentService();
$this->depositAmount = $paymentService->calculateDepositAmount($booking);
$this->paymentSummary = $paymentService->getPaymentSummary($booking);
$this->form->fill([
'payment_method' => 'stripe',
]);
}
public function form(Form $form): Form
{
return $form
->schema([
Select::make('payment_method')
->label('Payment Method')
->options([
'stripe' => 'Credit/Debit Card (Stripe)',
'paypal' => 'PayPal',
'bank_transfer' => 'Bank Transfer',
])
->required(),
Hidden::make('amount')
->default($this->depositAmount),
])
->statePath('data');
}
public function processPayment(): void
{
$data = $this->form->getState();
// Redirect to payment gateway based on method
if ($data['payment_method'] === 'stripe') {
return redirect()->route('client.payment.stripe', [
'booking' => $this->booking,
'amount' => $this->depositAmount,
]);
}
if ($data['payment_method'] === 'paypal') {
return redirect()->route('client.payment.paypal', [
'booking' => $this->booking,
'amount' => $this->depositAmount,
]);
}
}
}
```
### 3. Client Booking View - Payment Status
**File:** `resources/views/filament/client/pages/booking-details.blade.php`
```blade
<div class="space-y-6">
<!-- Payment Summary Card -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-semibold mb-4">Payment Summary</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600">Subtotal:</span>
<span class="font-medium">€{{ number_format($booking->subtotal_amount, 2) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Tax ({{ $taxRate }}%):</span>
<span class="font-medium">€{{ number_format($booking->tax_amount, 2) }}</span>
</div>
<div class="flex justify-between border-t pt-3">
<span class="text-lg font-semibold">Total:</span>
<span class="text-lg font-semibold">€{{ number_format($booking->total_amount, 2) }}</span>
</div>
@if($booking->deposit_amount > 0)
<div class="flex justify-between text-blue-600">
<span>Deposit Paid:</span>
<span class="font-medium">€{{ number_format($booking->deposit_amount, 2) }}</span>
</div>
@endif
<div class="flex justify-between text-green-600">
<span>Total Paid:</span>
<span class="font-semibold">€{{ number_format($booking->paid_amount, 2) }}</span>
</div>
@if($booking->remaining_amount > 0)
<div class="flex justify-between text-orange-600">
<span>Remaining Balance:</span>
<span class="font-semibold">€{{ number_format($booking->remaining_amount, 2) }}</span>
</div>
@endif
</div>
<!-- Payment Status Badge -->
<div class="mt-4">
@if($booking->payment_status === 'paid')
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
Fully Paid
</span>
@elseif($booking->payment_status === 'deposit_paid')
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
Deposit Paid - Balance Due at Check-in
</span>
@else
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-orange-100 text-orange-800">
Payment Pending
</span>
@endif
</div>
</div>
<!-- Payment History -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-semibold mb-4">Payment History</h3>
<div class="space-y-3">
@forelse($booking->payments as $payment)
<div class="flex items-center justify-between border-b pb-3">
<div>
<div class="font-medium">{{ $payment->payment_stage_label }}</div>
<div class="text-sm text-gray-600">
{{ $payment->paid_at->format('d M Y H:i') }}
</div>
<div class="text-xs text-gray-500">
{{ $payment->payment_method_label }}
@if($payment->transaction_id)
• ID: {{ $payment->transaction_id }}
@endif
</div>
</div>
<div class="text-right">
<div class="font-semibold text-green-600">
€{{ number_format($payment->amount, 2) }}
</div>
<div class="text-xs">
<span class="px-2 py-1 rounded text-white {{ $payment->status === 'completed' ? 'bg-green-500' : 'bg-gray-500' }}">
{{ $payment->status_label }}
</span>
</div>
</div>
</div>
@empty
<p class="text-gray-500 text-center py-4">No payments yet</p>
@endforelse
</div>
</div>
</div>
```
## Migration Steps
1. **Run the new migration:**
```bash
php artisan migrate
```
2. **Update existing bookings** (if any):
```bash
php artisan tinker
```
```php
use App\Services\BookingPaymentService;
$service = new BookingPaymentService();
\App\Models\Booking::chunk(100, function($bookings) use ($service) {
foreach ($bookings as $booking) {
$totals = $service->calculateBookingTotal($booking);
$booking->update([
'total_amount' => $totals['total'],
'subtotal_amount' => $totals['subtotal'],
'tax_amount' => $totals['tax_amount'],
]);
}
});
```
3. **Delete duplicate files** (no longer needed):
```bash
rm app/Models/BookingPayment.php
rm database/migrations/2025_10_23_150501_create_payments_table.php
```
## Summary
✅ **Unified payment system** - Single table for reservations & bookings
✅ **Payment stages** - Deposit, check-in, check-out tracking
✅ **Admin visibility** - Payment history, status, and actions
✅ **Client visibility** - Payment summary and history
✅ **Tax calculation** - Automatic based on Financial Settings
✅ **Payment methods** - Cash, card, online, bank transfer
✅ **Receipt tracking** - Regular and fiscal receipts
✅ **Multi-tenant** - Company and workspace scoped
The system is now ready for full implementation with UI components!