guides/braintree-metered-billing.md

# Braintree metered billing

Braintree does not expose Stripe-native meter objects. In Accrue, Braintree metering is therefore a **local aggregation** system with Braintree used only for external settlement.

## Shared ingress

Hosts still report raw usage through `Accrue.Billing.report_usage/3`:

```elixir
Accrue.Billing.report_usage(customer, "ai_tokens", value: 1200)
```

That API stays host-light. The raw event is durable input, not the final billing contract.

## Local meter definitions

Accrue binds each billable `event_name` to a local meter definition. That definition points at a concrete subscription item and stores the billing snapshot needed to explain the charge later.

This is the key processor distinction:

- Stripe can meter natively.
- Braintree needs Accrue-owned local truth to decide what is billable.

## Renewal window lifecycle

The primary trigger is webhook-first. When Braintree proves a subscription advanced to the next period, Accrue opens one immutable **renewal window** for the closed period.

If the webhook-primary path misses that close, `Accrue.Jobs.MeteredRenewalReconciler` provides a narrow repair backstop after a grace period. It exists to repair stale windows, not to replace the webhook clock.

## Local invoice first

At period close, Accrue aggregates matched usage into local invoice items before any external settlement happens.

That local invoice is the canonical explanation of the bill:

- matched events become priced invoice items
- unmatched events remain explicit operator-visible exceptions
- unusable events keep their error state instead of disappearing

## External settlement

After local invoice authoring, Accrue settles the closed window with one Braintree `Transaction.sale` against the customer's current default vaulted payment method.

The explanation should stay simple: Accrue calculated the bill locally, then used Braintree only to collect the payment.

## Recovery states

Metered settlement keeps one replay-safe charge unit per renewal window.

- `awaiting-payment-method`: the renewal is blocked until the customer repairs the default payment method.
- `failed-exhausted`: the renewal reached a terminal operator-visible failure state.

Recovery reuses the same renewal window instead of inventing a second bill.

If the surrounding webhook path needed repair first, do that work through the
same operator story documented in [`webhooks.md`](webhooks.md) and
[`operator-runbooks.md`](operator-runbooks.md): replay the persisted event
stream, let local projections converge, then retry settlement against the
existing renewal window.

## Operator signals

Watch these ops tuples and matching counters:

- `[:accrue, :ops, :metered_renewal_stale_repaired]`
- `[:accrue, :ops, :metered_missing_definition]`
- `[:accrue, :ops, :metered_charge_awaiting_payment_method]`
- `[:accrue, :ops, :metered_charge_failed_exhausted]`

See [`telemetry.md`](telemetry.md) for the exact metadata and metric names, and [`operator-runbooks.md`](operator-runbooks.md) for ordered triage.