This guide mirrors the checked-in examples/accrue_host story in package-facing terms. Your Phoenix app owns MyApp.Billing, routing, auth, runtime config, and verification choices. Accrue owns the billing engine behind those public boundaries. Read-only processor queries such as saved payment methods use Accrue.Billing.list_payment_methods/2 (and the host wrapper MyApp.Billing.list_payment_methods/2 after mix accrue.install); see guides/telemetry.md for the [:accrue, :billing, :payment_method, :list] span. Server-side checkout session creation uses Accrue.Billing.create_checkout_session/2 (and your host facade after install); telemetry is [:accrue, :billing, :checkout_session, :create] — see guides/telemetry.md#billing-checkout-session-create.

Customer Portal session creation is the parallel server-side helper for billing self-service.

Server-side billing portal session creation uses Accrue.Billing.create_billing_portal_session/2 (and your host facade after install); telemetry is [:accrue, :billing, :billing_portal, :create] — see guides/telemetry.md#billing-billing-portal-create.

Provider behavior stays honest across the shared facade:

ProviderCheckout session resultBilling portal result
StripeUpstream hosted URLUpstream hosted URL
BraintreeMounted local URLMounted local URL

This guide mirrors only the setup-critical needles from the canonical processor support matrix: the shared checkout and portal facade stays provider-honest, update_customer/2 remains a bounded provider-neutral write-through, cancel/2 is the shared immediate path, and the official active-subscription-change contract is swap_plan/3 plus preview_upcoming_invoice/2. Preview is the canonical path where supported before commit; swap_plan/3 is native on Stripe, testing/local-only on Fake, bounded on Braintree when the host configures :plan_resolver, and cancel_at_period_end/2 stays a Fake/Stripe-only scheduled-end path.

How to enter this guide

This guide is one spine with three entry capsules — pick where you are starting, then follow the same ordered story (deps → install → runtime → migrations → Oban → webhooks → admin → proof). Public wording and step order stay aligned with examples/accrue_host/README.md; when the spine or command vocabulary changes, update that README in the same pull request (D-02). Maintainer checklist (INT-11): same-PR capsule discipline lives in the contributor map scripts/ci/README.md — search for First Hour + host README capsule parity.

For maintenance posture (when to stop speculative doc work, how friction is intake-gated), see Maturity and maintenance.

Capsule H — Hex consumer

You already have a Phoenix app. Add Accrue to mix.exs, run mix deps.get, then mix accrue.install … and continue from § 1. First run below (runtime config → migrations → Oban → webhook route → admin mount → subscription + proof).

Capsule M — Monorepo clone

From the repository root: cd examples/accrue_host, run mix setup, start mix phx.server, then follow the numbered host README story (subscription → signed webhook → admin → mix verify) — the same Fake-backed arc this guide describes in package terms. Sigra is wired in the checked-in demo for convenience and reproducibility, not because production Accrue apps must use it.

Capsule R — Evaluate / read-only

Shortest read-only path: clone the repo, cd examples/accrue_host, run mix verify or mix verify.full. For merge-blocking VERIFY-01 detail and Playwright entry points, use #proof-and-verification in the host README when you need more than the bounded proof commands.

Trust boundary (production vs demo)

Production apps integrate billing through host-owned Accrue.Auth; see Auth adapters for adapter choices and wiring contracts. Sigra is optional: the demo uses it for deterministic organization billing and CI, not as a blanket production requirement. When you are not on Sigra, follow Capsule H and Organization billing (non-Sigra) for org-scoped Stripe customers. Demo-specific mix.exs and setup commands stay in examples/accrue_host/README.md.

When you are preparing a real deploy (not the demo loop), walk Production readiness once — it links the same guides in ship order without duplicating them.

Stripe-first spine, early Braintree branch

Stay on the default Stripe-hosted path unless you already know you need Braintree. Stripe remains the fastest first-user production route through this guide, while the bounded Braintree branch keeps the same public facade with mounted local checkout and portal behavior.

If you are using Braintree instead, branch early and treat the mounted portal contract as part of initial setup, not a later polish task:

  • add accrue_portal
  • mount accrue_portal "/billing" as a sibling scope beside accrue_admin
  • set portal_mount_path to the mounted portal route
  • set portal_base_url to an absolute host URL
  • configure :plan_resolver if you want first-party plan swaps through Accrue.Billing.swap_plan/3 or accrue_admin
  • use Accrue.Billing.preview_upcoming_invoice/2 as the canonical preview-before-commit path where supported; Braintree remains unsupported
  • keep auth/session continuity across the Plug and LiveView portal mounts
  • satisfy the Hosted Fields script and CSP contract before opening checkout

That branch stays intentionally short here. Use Braintree local portal for the full mounted-path setup, the packaged accrue_portal default story, and the sharp failure modes to expect when portal_mount_path, portal_base_url, auth/session state, or Hosted Fields readiness are wrong.

1. First run

The first hour should end with one Fake-backed subscription, one signed webhook proof, mounted admin inspection, and a focused verification pass.

Install the packages

Hex vs main: The version pins below mirror accrue/mix.exs and accrue_admin/mix.exs @version on the branch you are reading (usually main on GitHub). Hex.pm / Hex.pm/packages/accrue_admin reflect what is published; use HexDocs when you need docs tied to the resolved Hex version.

  1. The fenced ~> pins below track the Hex-published SemVer line for the @version pair this branch ships with.
  2. path: / monorepo installs must keep accrue and accrue_admin on the same three-part ~> (lockstep trains).
  3. Pre-1.0 ~> minors may still ship breaking API changes—treat mix.lock as the production stability boundary, not semver intuition alone.
defp deps do
  [
    {:accrue, "~> 1.1.0"},
    {:accrue_admin, "~> 1.1.0"}
  ]
end
mix deps.get
mix accrue.install --billable MyApp.Accounts.User --billing-context MyApp.Billing

The checked-in host example is the canonical local evaluation loop:

cd examples/accrue_host
mix setup
mix phx.server

Keep runtime config host-owned

Accrue raises Accrue.ConfigError when required setup is missing. Keep secrets and environment-specific values in config/runtime.exs:

import Config

config :accrue, :processor, Accrue.Processor.Fake

config :accrue, repo: MyApp.Repo

config :accrue, :webhook_signing_secrets, %{
  stripe: System.get_env("STRIPE_WEBHOOK_SECRET", "whsec_test_host")
}

Run your database setup before boot:

mix ecto.create
mix ecto.migrate

Invoice rendering now defaults to Rendro, so the normal invoice path does not require Chrome. Only hosts that explicitly choose the legacy Chromic compatibility path need ChromicPDF setup; the deeper renderer and migration details live in PDF Rendering.

Start Oban with the app so webhook dispatch and replay work end to end:

children = [
  MyApp.Repo,
  {Oban, Application.fetch_env!(:my_app, Oban)},
  MyAppWeb.Endpoint
]

Mount the public billing boundaries

Add signed webhook ingest at /webhooks/stripe and keep the handler on the public callback surface:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  import Accrue.Router

  pipeline :accrue_webhook_raw_body do
    plug Plug.Parsers,
      parsers: [:json],
      pass: ["*/*"],
      json_decoder: Jason,
      body_reader: {Accrue.Webhook.CachingBodyReader, :read_body, []}
  end

  scope "/webhooks" do
    pipe_through :accrue_webhook_raw_body
    accrue_webhook "/stripe", :stripe
  end
end

When this fails

defmodule MyApp.BillingHandler do
  use Accrue.Webhook.Handler

  @impl Accrue.Webhook.Handler
  def handle_event(type, event, ctx) do
    MyApp.Billing.handle_webhook(type, event, ctx)
  end
end

Mount accrue_admin "/billing" behind your host auth boundary. AccrueAdmin.Router.accrue_admin/2 is the public router macro:

import AccrueAdmin.Router

scope "/" do
  pipe_through [:browser, :require_authenticated_user]

  accrue_admin "/billing",
    session_keys: [:user_token],
    on_mount: [{MyAppWeb.UserAuth, :mount_current_user}]
end

Prove the first subscription and webhook

Create the first subscription through the generated facade:

user = MyApp.Accounts.get_user!(user_id)

{:ok, subscription} =
  MyApp.Billing.subscribe(user, "price_basic", trial_end: {:days, 14})

For app-level tests, stay on supported helpers:

use Accrue.Test

Then post one signed customer.subscription.created payload through /webhooks/stripe, visit /billing, and confirm the mounted admin UI shows the resulting billing state plus replay visibility.

Finish the guided path with the focused host proofs:

mix verify

mix verify is the focused tutorial proof suite. mix verify.full is the CI-equivalent local gate that adds compile, assets, dev boot, regression, and browser smoke after the first-run story is already clear. For the authoritative merge-blocking command matrix, VERIFY-01, and Playwright entry points, see Proof and verification in the host demo README.

2. Seeded history

Seeded history is for deterministic replay/history evaluation, not for the main teaching path.

cd examples/accrue_host
mix setup
mix verify.full

Use it when you need replay-ready webhook states, browser smoke fixtures, or other evaluation setup that should not become public integration guidance.

3. Focused verification

  • mix verify proves the host-owned tutorial arc: installer boundary, first subscription through MyApp.Billing, signed webhook ingest, mounted /billing inspection, and replay visibility.
  • mix verify.full is the CI-equivalent local gate for maintainers.
  • bash scripts/ci/accrue_host_uat.sh is the repo-root wrapper around that same full contract.
  • bash scripts/ci/accrue_host_hex_smoke.sh is Hex smoke and stays separate from the checked-in host demo.
  • mix accrue.install remains the production setup command for your own host app.

4. Rerunning mix accrue.install

Reruns refresh pristine generated files that still match the Accrue fingerprint marker; user-edited generated files are skipped so local policy changes are preserved. Unmarked existing files stay skipped unless you opt into a narrow overwrite, and --write-conflicts writes reviewable artifacts under .accrue/conflicts/ instead of patching live files blindly — the same contract as the upgrade guide. See Upgrade guide — Installer rerun behavior for the full installer rerun semantics.