Braintree metered billing

Copy Markdown View Source

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:

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 and 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 for the exact metadata and metric names, and operator-runbooks.md for ordered triage.