# Corporate Phase 6.5 Invoice Security Audit

## Scope

Reviewed the Phase 6 corporate invoicing surface:

- `/admin/corporate-invoices`
- `/admin/corporate-payments`
- `/admin/corporate-finance/receivables`
- `/org/invoices`
- invoice PDF downloads
- payment receipt downloads

## Route And Permission Findings

| Area | Status | Evidence | Action |
| --- | --- | --- | --- |
| Admin corporate invoices | PASS | Routes use `auth` group and route-level `can:corporate_invoices.*` / `can:view,invoice` / `can:download,invoice`. | No route change required. |
| Admin corporate payments | PASS | Routes use `auth` group and `can:corporate_payments.view/create` plus `CorporatePaymentPolicy` for detail actions. | Added regression tests for review/allocate permissions. |
| Corporate receivables | PASS | `/admin/corporate-finance/receivables` requires `can:corporate_receivables.view`. | Added regression test for support without permission. |
| Organization invoices | HARDENED | `/org/invoices` is under org tenancy middleware. Component now also requires `org.invoices.view` before rendering invoice data. | Fixed component-level visibility. |
| Invoice PDF download | PASS | Admin and org download routes go through controller + `CorporateInvoicePolicy::download`. | Added IDOR and activity logging tests. |
| Corporate payment receipt download | NOT IMPLEMENTED | `corporate_payments.receipt_path` exists but there is no corporate receipt download route yet. | No Phase 6.5 change; future route must use private disk + `CorporatePaymentPolicy::downloadReceipt`. |
| Legacy company fields | PASS | Corporate invoicing code uses `organization_id`; no `companies`, `users.company_id`, or `agreement_path` references in Phase 6 invoice/payment code. | No change required. |

## Financial Integrity Findings

| Finding | Risk | Fix |
| --- | --- | --- |
| A confirmed payment could be allocated to the same invoice more than once. | HIGH: duplicate operational allocation and duplicate recognized revenue across multiple allocation rows. | `CorporatePaymentAllocationService` now rejects an existing payment/invoice pair before creating a second allocation. |
| Draft invoices could receive allocation through service-level calls. | HIGH: revenue could be recognized before an invoice is officially issued. | Allocation is now restricted to `issued`, `partially_paid`, and `overdue` invoices. |
| Allocation across organizations was already blocked. | PASS | Regression test added. |
| Allocation above invoice balance or payment remainder was already blocked. | PASS | Regression tests expanded. |
| Unconfirmed payments were already blocked. | PASS | Regression test added. |
| Financial transactions use allocation id as source. | PASS | `source_type=corporate_payment_allocation` and `source_id=allocation.id` keep revenue idempotent per allocation. |

## Org Portal IDOR Findings

| Scenario | Status |
| --- | --- |
| Org member cannot see another organization invoice in `/org/invoices`. | PASS |
| Org member cannot download another organization invoice PDF. | PASS |
| Org member without `org.invoices.view` cannot see invoice references. | HARDENED |
| Invoice PDFs are stored outside public disk. | PASS |
| Invoice PDF download records activity. | PASS |

## UI/UX Polish Applied

- Corporate payment detail now shows remaining unallocated payment amount.
- Allocation rows show invoice balance and enforce HTML `max` based on payment remainder and invoice balance.
- Allocation UI has an explanatory safety hint.
- Empty states added for invoice index and payment allocation when no open invoices exist.
- Receivables page now shows aging buckets.
- Badges and financial cards were made clearer on invoice/payment pages.

## Remaining Limitations

- No credit notes.
- No public invoice links.
- No corporate payment gateway.
- No corporate receipt download route yet.
- No allocation reversal feature; if added later, it must reverse or void the related financial transaction instead of deleting history.
