# agent charged the deposit and the full amount: double billing on milestone invoices

## TL;DR
Model the deposit as a credit against the milestone total: the final invoice bills total-minus-deposit, never the full amount again, and each milestone's state machine allows exactly one charge per stage. If both already charged, refund the deposit portion and fix the flow. A milestone is a state machine; charging is a transition that must happen once.

```text
agent charged the deposit and the full amount: double billing on milestone invoices
```

## Fix it

1. **Track milestone state explicitly.** Each milestone has a status: `deposit_due`, `deposit_paid`, `balance_due`, `paid`. Only the transition matching the current status may charge. Success check: attempting the balance charge while status is `deposit_due` is rejected.
2. **Compute the balance as total minus deposit.** The final invoice amount is derived, never typed: `balance = milestone_total - deposit_paid`. Success check: a 10k milestone with a 2k deposit produces an 8k balance invoice.
3. **Gate each charge on a recorded payment.** Before charging a stage, verify no successful payment exists for that stage. Success check: replaying the deposit step after success is a no-op.
4. **Recover a double bill.** Refund the duplicate (usually the deposit amount double-counted in the full charge), and send the customer a corrected statement. Success check: the customer's total paid equals the milestone total, not total plus deposit.

## When this applies

- Milestone, phased, or deposit-then-balance billing built or run by an agent.
- A customer was charged the deposit and then the full milestone amount.

## When it doesn't

- The deposit was always meant to be non-refundable and additional; check the contract terms first.
- The duplicate is two identical full charges; that's an idempotency bug, not a milestone bug.

## Compatibility

- Pattern: agent-built milestone billing on Stripe Invoices, QuickBooks, Xero, or custom ledger flows.
- The state-machine guard is provider-agnostic; implement it in your own order store.

### Variant: agent charged the deposit twice

Same stage charged twice. Same fix with emphasis on step 3: the per-stage payment record makes the second attempt a no-op.

### Variant: final invoice ignored a partial deposit payment

The deposit was paid in two parts and the balance math used only one. Same fix: sum ALL recorded deposit payments before computing the balance.

## Why it happens

Agents see "deposit invoice" and "milestone invoice" as two independent tasks and charge each for its face amount. Nobody subtracts, because the subtraction lives in no explicit state; it's assumed to be obvious, and it isn't.

## Edge cases

- Deposit paid late: if the deposit lands after the balance invoice was issued, void and reissue the balance rather than stacking payments.
- Multi-milestone projects: scope the state machine per milestone, not per project, or milestone 2's deposit collides with milestone 1's balance.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst__Q81NYLgkrFA0UT94OqaMg
