Net after fuel, labor, equipment, and Stripe. On every job, automatically

Every quote returns net-after-Stripe-fees and equipment cost. Every job persists fuel, labor, and equipment cost columns at booking time. The bookkeeping panel rolls them up against actual Stripe payouts so the numbers reconcile.

Below: seven shipped pieces of the cost-tracking pipeline, with column and route citations for each.

Seven pieces of the cost-tracking pipeline

Each piece is shipped. Open the linked file or admin URL to audit.

  1. 01

    Every quote returns net-after-fees explicitly

    The `/api/quote` response includes `net_after_fees` on every quote — calculated as `quoted_price - (quoted_price × 0.029 + 0.30)` for Stripe-collected jobs. The customer sees the gross price; you see what actually lands in your bank account. The booking-page summary shows it as "You receive: $X" alongside the customer-facing "Total: $Y" so there’s no end-of-month surprise about Stripe’s cut.

    stripeNetAfterFees() in packages/shared/src/pricing.ts; net_after_fees on POST /api/quote response

  2. 02

    Card-fee passthrough flips who pays Stripe

    If you turn on `pass_card_fee_to_customer` at the service level, the engine adds the 2.9% + 30¢ to the customer’s quoted price and `net_after_fees` becomes the full quoted amount — because the customer absorbed Stripe’s cut. `card_fee_included: true` rides on the quote response so the booking page can render "$X (card fee included)" cleanly. You choose: eat the fee and net less, or pass it through and net the full quoted price.

    pass_card_fee_to_customer flag; card_fee_included on /api/quote response

  3. 03

    Equipment cost is computed on every quote

    Each service can have equipment assigned to it (riding mower, edger, blower). Each `equipment_type` has a `cost_per_hour` value. The quote engine multiplies hours (from `estimated_time_mins / 60`, or a per-equipment `hours_override`) by the cost-per-hour and returns the total as `equipment_cost` on the quote response. The number persists on the job row when the customer books.

    equipment_types.cost_per_hour; calculateEquipmentCost() in pricing.ts; equipment_cost on /api/quote and jobs.equipment_cost column

  4. 04

    Every job persists four cost columns

    When a job is created the engine writes `fuel_cost`, `labor_cost_drive`, `labor_cost_job`, and `equipment_cost` into the row alongside `distance_miles`, `drive_time_mins`, `estimated_time_mins`, and `actual_time_mins`. These aren’t calculated on the fly in a report — they’re snapshotted at booking time so historical jobs reflect the costs as they were that day, not as your settings are today.

    jobs.fuel_cost, jobs.labor_cost_drive, jobs.labor_cost_job, jobs.equipment_cost columns

  5. 05

    Cost inputs live in /admin/settings

    `/admin/settings` → Labor & Fuel has the two inputs that drive the cost columns: `default_labor_rate_per_hour` ($/hr — typically your loaded cost: wage + payroll tax + insurance) and `fuel_mpg` (vehicle fuel economy). Distance is geocoded; drive time is estimated; the resulting fuel and labor costs are written to the job row at booking. Edit the rates seasonally if your costs shift — historical jobs keep their snapshotted values.

    tenants.settings.default_labor_rate_per_hour, tenants.settings.fuel_mpg

  6. 06

    The admin jobs table shows Stripe-net per completed job

    The completed-jobs view in `/admin/jobs` renders the Stripe-net amount (`final_price × (1 - 0.029) - 0.30`) in the rightmost column for every completed row. It’s the same calculation the quote engine returns on `net_after_fees`, applied to the actual `final_price` after any on-site adjustments. You see, line by line, what each job put into your bank account after Stripe.

    JobsTable.tsx renders final_price × (1 - 0.029) - 0.30 for completed jobs

  7. 07

    Period profit rollup lives in /admin/bookkeeping

    The bookkeeping panel aggregates the persisted cost columns across all jobs whose payments hit the selected period (dedup by job_id). It surfaces total fuel, total labor, total equipment, total miles, and the resulting net profit (gross job income minus the rolled-up costs). The export ties to Stripe payouts so the numbers reconcile against what landed in your bank.

    /admin/bookkeeping aggregates jobs.fuel_cost + labor_cost_job + equipment_cost per payment period

The math, in plain terms

Stripe net. quoted_price − (quoted_price × 0.029 + 0.30) — the deduction Stripe takes from every card charge. This is the number returned as net_after_fees on the quote and shown in the jobs table for every completed job.

Labor cost. estimated_time_mins / 60 × default_labor_rate_per_hour — snapshotted into jobs.labor_cost_job at booking. labor_cost_drive tracks the round-trip drive labor separately.

Equipment cost. Σ (hours × equipment_types.cost_per_hour) for every piece of equipment assigned to the service — written to the quote as equipment_cost and persisted on the job row.

Fuel cost. round-trip distance / fuel_mpg × per-gallon cost — persisted as jobs.fuel_cost.

Period profit. Σ payments − Σ (fuel_cost + labor_cost_job + equipment_cost) over the selected period, dedup’d by job_id — what /admin/bookkeeping displays alongside the Stripe payout reconciliation.

Where each number surfaces

  • /api/quote net_after_fees, equipment_cost, card_fee_included, below_target_rate on every quote response.
  • /admin/jobs — Stripe-net column on every completed job; click into the job detail for the full line-item breakdown, the target-rate flag, and operator price adjustments.
  • /admin/bookkeeping — period profit rollup with fuel / labor / equipment / miles totals, reconciled against Stripe payouts; CSV export for accounting.
  • /admin/settings → Labor & Fuel — the two cost inputs (`default_labor_rate_per_hour`, `fuel_mpg`) that drive the snapshotted cost columns at booking time.

Profit-per-job FAQs

Why is "profit" rolled up by period in bookkeeping instead of shown per row on the jobs table?+
Per-row profit needs a stable cost basis, and labor / fuel / equipment costs are noisy at the single-job level — drive time can be off by 20%, equipment usage doesn’t always match the estimated hours, and operator-on-site adjustments shift the final price. The bookkeeping rollup smooths that noise across a payment period (typically a week or a month) and reconciles against actual Stripe payouts. The jobs table shows Stripe-net per job — what hit the bank — which is the number you actually trust. The full cost-subtracted view lives in `/admin/bookkeeping` where the math has enough data to be honest. We chose visible-Stripe-net + period-profit-rollup over a noisy per-row profit number that operators would have to caveat in their head.
How is the Stripe cut shown on the customer-facing quote?+
It isn’t, unless you turn on card-fee passthrough. The customer sees the gross quoted price; the booking page shows "Total: $X" and a Stripe checkout. If you have `pass_card_fee_to_customer` enabled, the customer sees "$X (card fee included)" and `card_fee_included: true` rides on the quote response so the breakdown is clean. Either way, your side of the booking-page summary shows `net_after_fees` as "You receive: $Y" so you know what to expect.
What about payment_collection = external — jobs you take by cash or check?+
No Stripe fee deduction applies; `net_after_fees` equals `quoted_price`. The job still gets the cost columns written (`fuel_cost`, `labor_cost_*`, `equipment_cost`) because those are about the job’s economics, not the payment method. The bookkeeping panel still rolls them up. The Stripe-net column on the admin jobs table is meaningful even for external-collection jobs — it shows what would have been the Stripe net — but most external-collection operators look at the bookkeeping rollup instead.
How accurate is the labor cost — you’re using estimated_time_mins, not actual?+
The labor cost on the job row is snapshotted at booking from `estimated_time_mins × default_labor_rate_per_hour`. When the operator marks a job complete, `actual_time_mins` gets written to the same row — both numbers are there. The bookkeeping rollup uses the snapshotted cost (which is what you committed to at booking); the actual time gives you the variance signal. If actual is consistently 20% over estimate on a service, that’s information you can use to retune the `estimated_time_mins` formula or the labor rate.
What about fuel price — gas is not the same per gallon everywhere.+
The fuel-cost snapshot uses `fuel_mpg` from your settings and the geocoded distance. The settings panel today doesn’t expose a per-gallon price as a tenant input — the cost is rolled in via the operational rate you set in `default_labor_rate_per_hour` if you choose to load fuel into labor, or surfaces as the explicit `fuel_cost` column the engine writes. If you want gas at $3.10 vs $4.20 to shift your numbers seasonally, today the practical move is to bump `default_labor_rate_per_hour` to absorb the rising operational cost; an explicit gas-price setting is on the list, not shipped.
Can my bookkeeper export this for QuickBooks?+
Yes. `/admin/bookkeeping` has a CSV export that includes the line-item breakdown per payment, the cost columns per job, and the period totals. Most operators’ bookkeepers reconcile the CSV against Stripe payouts and book the labor/fuel/equipment lines as expense buckets in QuickBooks. (Direct QuickBooks Online sync is built and going through Intuit certification; when it ships, it imports on the same plan at the same price.)
Does the operator queue show profit?+
No — the crew mobile app shows job priority, address, lawn outline, estimated time, and assigned equipment, but not the profitability flag. The target-hourly-rate flag (`below_target_rate`) surfaces on the admin job detail so the office can flag a job before the crew leaves; the crew themselves don’t see profit numbers. That’s deliberate — operators shouldn’t be deciding which jobs to skip or rush based on margin while they’re on a property.

See net-after-fees on your own pricing

14-day free trial. Set your labor rate, your fuel MPG, and equipment cost-per-hour; run a real booking; read the cost columns on the job row and the rollup in /admin/bookkeeping.

14-day free trial · No card required · Cancel any time