Overview
The PaymentController handles payment recording and tracking for medical services. It supports multiple payment methods and can link payments to specific consultations.
Controller Location: app/Http/Controllers/PaymentController.php
Base Route: /payments
Middleware: auth, verified, role:admin|receptionist
Endpoints
List Payments
Route Name: payments.index
Retrieve a paginated list of all payments with patient and consultation information.
Query Parameters
Search by patient full name (partial match)
Response
Returns an Inertia response rendering payments/index with:
Paginated payment collection (10 per page) Show Payment object structure
Associated consultation ID (nullable)
Payment method: cash, card, or transfer
Payment status: paid, pending, or cancelled
Related patient information
Related consultation information (if linked)
Example Request
GET /payments?search=John
Implementation
public function index ( Request $request )
{
$query = Payment :: with ([ 'patient' , 'consultation' ]) -> latest ();
if ( $request -> has ( 'search' )) {
$search = $request -> get ( 'search' );
$query -> whereHas ( 'patient' , function ( $q ) use ( $search ) {
$q -> where ( 'full_name' , 'like' , "%{ $search }%" );
});
}
return Inertia :: render ( 'payments/index' , [
'payments' => $query -> paginate ( 10 ) -> withQueryString (),
'filters' => $request -> only ([ 'search' ]),
]);
}
Create Payment
Route Name: payments.store
Record a new payment transaction.
Request Body
ID of the patient making the payment
ID of the associated consultation (optional)
Payment amount (minimum: 0)
Payment method: cash, card, or transfer
Payment status: paid, pending, or cancelled
Optional payment notes or description
Response
Redirects back with success message: “Pago registrado correctamente.”
Example Request
POST /payments
Content-Type: application/json
{
"patient_id" : 15,
"consultation_id" : 42,
"amount" : 150.00,
"payment_method" : "card",
"status" : "paid",
"notes" : "Payment for consultation - March 2026"
}
Validation Rules
[
'patient_id' => 'required|exists:patients,id' ,
'consultation_id' => 'nullable|exists:consultations,id' ,
'amount' => 'required|numeric|min:0' ,
'payment_method' => 'required|string|in:cash,card,transfer' ,
'status' => 'required|string|in:paid,pending,cancelled' ,
'notes' => 'nullable|string' ,
]
Implementation
public function store ( Request $request , CreatePaymentAction $action )
{
$validated = $request -> validate ([
'patient_id' => 'required|exists:patients,id' ,
'consultation_id' => 'nullable|exists:consultations,id' ,
'amount' => 'required|numeric|min:0' ,
'payment_method' => 'required|string|in:cash,card,transfer' ,
'status' => 'required|string|in:paid,pending,cancelled' ,
'notes' => 'nullable|string' ,
]);
$action -> execute ( $validated );
return redirect () -> back () -> with ( 'success' , 'Pago registrado correctamente.' );
}
Authorization
Only users with admin or receptionist roles can access payment endpoints.
Route :: middleware ([ 'role:admin|receptionist' ]) -> group ( function () {
Route :: get ( 'payments' , [ PaymentController :: class , 'index' ]);
Route :: post ( 'payments' , [ PaymentController :: class , 'store' ]);
});
Payment Methods
The system supports three payment methods:
Cash Cash payment at the clinic
Card Credit or debit card payment
Transfer Bank transfer or wire payment
Payment Statuses
Payment has been completed and verified.
Payment is expected but not yet received (e.g., pending bank transfer).
Payment was cancelled or refunded.
Action Classes
The controller uses an action class for payment creation:
CreatePaymentAction (app/Actions/Payments/CreatePaymentAction.php)
This separates business logic from the controller for better testability and reusability.
Use Cases
Recording a Consultation Payment
// Frontend: After consultation is complete
const paymentData = {
patient_id: consultation . patient_id ,
consultation_id: consultation . id ,
amount: 150.00 ,
payment_method: 'card' ,
status: 'paid' ,
notes: `Payment for consultation ${ consultation . id } `
};
await axios . post ( '/payments' , paymentData );
Recording a General Payment
// Payment not linked to consultation (e.g., registration fee)
const paymentData = {
patient_id: patient . id ,
consultation_id: null ,
amount: 50.00 ,
payment_method: 'cash' ,
status: 'paid' ,
notes: 'Patient registration fee'
};
await axios . post ( '/payments' , paymentData );
Searching Payments
// Search by patient name
const searchQuery = 'Maria' ;
const response = await axios . get ( `/payments?search= ${ searchQuery } ` );