Symptom
Three warning strings render on invoices that have never received a payment because outstanding = 100% of expected, and the underpayment math returns 100% — which trips hasSignificantUnderpayment() and requiresClientUnderpayAlert(). The copy reads as alarmist for a state that should just be "awaiting payment."
Observed strings:
-
Issuer view (resources/views/invoices/show.blade.php:404):
Underpayment detected — the outstanding balance exceeds the tolerance. Follow up with the client or record a manual adjustment.
-
Issuer view (resources/views/invoices/show.blade.php:417):
Client alert will show on the public invoice (underpayment ~100.0%). Follow up with the client or adjust the balance.
-
Client/public view (resources/views/invoices/partials/print/content.blade.php:338):
An outstanding balance of roughly 100.0% remains. Please send the remaining amount or contact {biller} for assistance.
List is probably not exhaustive — audit anywhere these methods/flags are read.
Root cause (likely)
In app/Models/Invoice.php:
hasSignificantUnderpayment() (line 360) and underpaymentPercent() (line 394) only guard against $expectedUsd <= 0, not against "no payment has occurred yet." When $confirmedUsd === 0 and $expectedUsd > 0, deficit equals the full expected amount → 100% → trips tolerance.
requiresClientUnderpayAlert() (line 416) is a thin wrapper over underpaymentPercent() so it inherits the same false-positive.
Fix direction
Short-circuit underpayment detection when no payment activity exists (no confirmed and no pending payments). A brand-new invoice is "awaiting payment," not "underpaid."
Audit all call sites of hasSignificantUnderpayment(), underpaymentPercent(), and requiresClientUnderpayAlert() to confirm consistent behavior.
Surfaces affected
- Issuer invoice view (
invoices/show.blade.php)
- Client public invoice view (
invoices/partials/print/content.blade.php)
- Possibly mail templates and notification surfaces — confirm during fix.
Why now
Surfaced during the how-to-invoice video script pass — looking at a freshly created invoice for the demo, the public/issuer views show alarming "underpayment detected" / "100% outstanding" copy that's confusing and incorrect for a not-yet-paid invoice.
Symptom
Three warning strings render on invoices that have never received a payment because outstanding = 100% of expected, and the underpayment math returns 100% — which trips
hasSignificantUnderpayment()andrequiresClientUnderpayAlert(). The copy reads as alarmist for a state that should just be "awaiting payment."Observed strings:
Issuer view (
resources/views/invoices/show.blade.php:404):Issuer view (
resources/views/invoices/show.blade.php:417):Client/public view (
resources/views/invoices/partials/print/content.blade.php:338):List is probably not exhaustive — audit anywhere these methods/flags are read.
Root cause (likely)
In
app/Models/Invoice.php:hasSignificantUnderpayment()(line 360) andunderpaymentPercent()(line 394) only guard against$expectedUsd <= 0, not against "no payment has occurred yet." When$confirmedUsd === 0and$expectedUsd > 0, deficit equals the full expected amount → 100% → trips tolerance.requiresClientUnderpayAlert()(line 416) is a thin wrapper overunderpaymentPercent()so it inherits the same false-positive.Fix direction
Short-circuit underpayment detection when no payment activity exists (no confirmed and no pending payments). A brand-new invoice is "awaiting payment," not "underpaid."
Audit all call sites of
hasSignificantUnderpayment(),underpaymentPercent(), andrequiresClientUnderpayAlert()to confirm consistent behavior.Surfaces affected
invoices/show.blade.php)invoices/partials/print/content.blade.php)Why now
Surfaced during the how-to-invoice video script pass — looking at a freshly created invoice for the demo, the public/issuer views show alarming "underpayment detected" / "100% outstanding" copy that's confusing and incorrect for a not-yet-paid invoice.