5.8 KiB
Payment Security Fixes
Overview
Security enhancements have been added to the payment flow in app/Http/Livewire/Pay.php to prevent tampering with front-end properties via browser developer tools or by modifying Livewire component state.
Fixes Applied
1. Balance Limit Check Now Blocks Execution
Location: Pay.php, checkBalanceLimits() method
What was wrong:
- The method set
isPaymentLimitErrorModalOpen = truebut returnedtrue doTransfer()didn't check the return value, so execution continued- A user could tamper with the check by modifying
isPaymentLimitErrorModalOpenin the browser console
What was fixed:
checkBalanceLimits()now returns a boolean:trueif limits OK,falseif exceededdoTransfer()now checks the return value:if (!$limitsOk) { return; }- If limits are exceeded, the transaction is NOT saved and a security warning is logged
Test Scenario:
// Before fix: User could bypass by setting in browser console:
Livewire.find('pay-component').isPaymentLimitErrorModalOpen = false;
// And payment would still be saved!
// After fix: Even if user tampersthis property, the backend check prevents saving
2. Amount Validation
Location: Pay.php, doTransfer() method
What was added:
// Security: Validate amount is a valid positive number
if (!is_numeric($amount) || $amount <= 0) {
$warningMessage = 'Security: Invalid payment amount (must be positive number)';
return $this->logAndReport(...);
}
Prevents:
- Negative amounts (e.g.,
amount = -50) - Zero amount (e.g.,
amount = 0) - Non-numeric values (e.g.,
amount = 'abc')
3. FromAccountId Validation
Location: Pay.php, doTransfer() method (added first)
What was added:
// Security: Verify fromAccountId is an integer (prevent tampering)
if (!is_int($fromAccountId) || $fromAccountId <= 0) {
$warningMessage = 'Security: Invalid From account ID';
return $this->logAndReport(...);
}
Prevents:
- Non-integer account IDs
- Negative or zero account IDs
- String values passed as account ID
Note: This validation happens BEFORE the ownership check, catching obvious tampering early.
4. ToAccountId Validation
Location: Pay.php, doTransfer() method
What was added:
// Security: Verify toAccountId is an integer (prevent tampering)
if (!is_int($toAccountId) || $toAccountId <= 0) {
$warningMessage = 'Security: Invalid To account ID';
return $this->logAndReport(...);
}
Prevents:
- Same issues as FromAccountId validation
5. Existing Ownership Check (Still in Place)
Location: Pay.php, doTransfer() method
This was already in place and remains:
$accountsInfo = collect($transactionController->getAccountsInfo());
if (!$accountsInfo->contains('id', $fromAccountId)) {
$warningMessage = 'Unauthorized payment attempt: illegal access of From account';
return $this->logAndReport(...);
}
Why this is important:
- Even though the browser interface only shows accounts the user owns, a user could tamper with Livewire to send a
fromAccountIdfor an account they don't own - This backend check verifies the FromAccountId belongs to the active session profile
- The check happens AFTER type validation but BEFORE the transaction is saved
Security Log Messages
All security violations are logged with a [TRANSFER] prefix. To check for security violations:
grep "\[TRANSFER\].*Security" storage/logs/laravel-*.log
Example log entries:
[2026-06-06 14:30:45] local.WARNING: [TRANSFER] Security: Invalid payment amount (must be positive number)
[2026-06-06 14:31:12] local.WARNING: [TRANSFER] Security: Invalid From account ID
[2026-06-06 14:32:33] local.WARNING: [TRANSFER] Security: Invalid To account ID
Validation Order
The checks happen in this order in doTransfer():
- Permission check - Does user have right to make payments?
- Amount validation - Is amount numeric and positive?
- FromAccountId type check - Is it a positive integer?
- ToAccountId type check - Is it a positive integer?
- Account existence check - Do both accounts exist in DB?
- Account ownership check - Does FromAccountId belong to active profile?
- Accountable check - Are both account holders not removed?
- Same account check - Are From and To different?
- Transaction type check - Is type allowed?
- Balance limits check - Does payment exceed limits?
If ANY check fails, execution stops and the transaction is NOT saved.
What's NOT Fixed (By Design)
These are intentionally NOT checked in the backend because they're enforced by the database schema:
- Transaction immutability - Transactions cannot be modified/deleted (DB user permissions)
- Account balance accuracy - Balance is calculated from immutable transactions (MySQL window functions)
- Transaction type validity - Enforced by foreign key constraint
Testing
Manual Testing Instructions:
-
Make a normal payment to verify it works
-
Open browser console and try:
// Try to set negative amount Livewire.find('pay-component-id').set('amount', -50); // Click Pay button - should be blocked with security log entry -
Try to inject invalid account IDs:
Livewire.find('pay-component-id').set('toAccountId', -1); // Should be blocked with security log entry -
Check logs for security violations:
tail -f storage/logs/laravel-$(date +%Y-%m-%d).log | grep "\[TRANSFER\]"
Related Code
PaymentSecurityTest.php- Automated tests for security validations (if needed)app/Http/Controllers/TransactionController.php- API endpoint that also validates paymentsapp/Observers/TransactionObserver.php- Clears caches on successful transaction creation