Today was all about following money through a system. The freelancer invoicing platform we've been working on needed a new feature: expenses that get reimbursed without fees.
The problem sounds simple. A freelancer pays for something on behalf of their client—a train ticket, software license, whatever. They want that money back at cost, not reduced by social fees and commission. The solution turned out to touch almost every layer of the stack.
Understanding the Money Flow
Before diving into the implementation, it helps to understand how payments move through the system. When a client pays an invoice, the money doesn't go directly to the freelancer. Instead, it passes through several stages.
First, the payment arrives at the invoice provider. From there, it moves into what we call the "void pool"—a holding account where the full amount sits before distribution. The system then calculates fees, taxes, and pension contributions before transferring the remainder to the user's pool. Finally, the freelancer can withdraw to their bank account.
The challenge with reimbursable expenses: they need to bypass the fee calculation entirely. They should go straight from the void pool to the user pool at face value, untouched by commission or social fees.
The Database Foundation
The implementation started with two database migrations. The first added a boolean flag to the expense table, allowing each expense to be marked as reimbursable. The second added a field to track the reimbursable amount on each payment record.
We store all monetary amounts in minor units—cents for dollars, öre for Swedish kronor. This avoids floating-point precision issues that plague financial calculations. Both new fields default to zero, ensuring existing records continue working without modification.
Splitting the Expense Totals
With the database ready, the invoice entity needed logic to distinguish between regular and reimbursable expenses. Previously, there was a single method that summed all expenses on an invoice. We split this into two separate calculations.
The first method now returns only regular expenses—those that should be included in fee calculations and appear on the client-facing invoice. The second method returns only reimbursable expenses—the pass-through amounts that bypass fees entirely.
This separation might seem redundant, but explicit is better than clever. When debugging financial calculations six months from now, having two clearly-named methods beats a single method with conditional logic.
The Bug That Almost Shipped
Here's where things got interesting—and almost embarrassing. We had the calculations working perfectly. The UI showed the correct numbers. The database stored the right values. But there was a critical flaw: the money wouldn't have actually moved.
The payment processor is responsible for moving money between pools. It handles the main salary transfer—taking funds from the void pool, subtracting fees, and depositing the remainder into the user's pool. Our implementation calculated the reimbursable amounts correctly but never created a transfer instruction for them.
The fix required adding an explicit transfer for reimbursable expenses. When a payment includes reimbursable amounts, the processor now creates a separate transfer from the void pool directly to the user's pool. This transfer carries a label identifying it as reimbursable expenses, making it traceable in transaction logs.
The key insight: the void pool holds the complete payment amount. Every destination—whether it's the user's pool, the fee account, or anywhere else—needs an explicit transfer instruction. Nothing moves automatically.
Revenue Reporting Implications
Another subtle issue emerged in the revenue reports. The existing query summed all expenses when calculating freelancer revenue. But reimbursable expenses aren't revenue—they're pass-through amounts that the freelancer paid out of pocket and is getting back at cost.
The fix was straightforward: add a filter to the expense join that excludes reimbursable items. The revenue calculations now accurately reflect actual earnings, not reimbursed costs.
Administrative Oversight
Reimbursable expenses introduce a trust element. A freelancer could potentially inflate expense amounts, knowing they'll receive that money without any fee deduction. To mitigate this, we added a requirement: invoices with reimbursable expenses need administrative approval.
When the system detects an invoice contains reimbursable items, it flags it for review. An administrator must verify that the receipts match the claimed amounts before the invoice can proceed through the normal workflow. This adds friction, but appropriate friction. Fee-free money movements warrant human verification.
The Calculator Flow
The invoice amount calculator orchestrates the complex dance of deductions and additions that transform a gross invoice amount into a net payout. Understanding this flow is essential for debugging any payment discrepancy.
The calculation proceeds in stages. First, VAT is removed from the gross amount. Then regular expenses and commission are subtracted. Pension contributions and employer taxes are calculated. Income tax is withheld. Finally—and this is the crucial part—reimbursable expenses are added back at the very end.
By adding reimbursable amounts after all fee calculations, we ensure they're never touched by percentage-based deductions. The freelancer receives exactly what they spent, plus their calculated net salary.
Frontend Integration
The user interface changes were relatively straightforward. The expense form received a new checkbox allowing freelancers to mark an expense as reimbursable. The invoice totals display now separates regular expenses from reimbursable ones.
One important detail in the frontend logic: the gross invoice total only includes regular expenses. Reimbursable amounts don't appear on the client-facing invoice—those are strictly between the platform and the freelancer. This prevents confusion for clients who might wonder why they're being charged for expenses they didn't agree to.
Localization
The platform serves Swedish freelancers, so translations were needed for both the frontend React application and the backend Symfony services. The Swedish term "Återbetalbara utlägg" clearly communicates the concept of reimbursable expenses to users.
Lessons Learned
Money systems are unforgiving. The logic can be correct in isolation but wrong in the actual flow. Every path needs end-to-end verification.
The implementation touched database schemas, backend business logic, payment processing, revenue reporting, administrative workflows, frontend calculations, and localization. Each layer had to correctly understand and handle the concept of fee-free reimbursement.
The diagrams helped—not just for documentation, but for finding gaps in the mental model. Drawing out the money flow revealed the missing transfer before any real funds were at risk. Sometimes the best debugging tool is a whiteboard.