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.
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
defp deps do
[
{:accrue, "~> 0.1.2"},
{:accrue_admin, "~> 0.1.2"}
]
endmix deps.get
mix accrue.install --billable MyApp.Accounts.User --billing-context MyApp.Billing
The checked-in host example is the canonical local evaluation path:
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
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
enddefmodule 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
endMount 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}]
endProve 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.TestThen 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.
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 verifyproves the host-owned tutorial arc: installer boundary, first subscription throughMyApp.Billing, signed webhook ingest, mounted/billinginspection, and replay visibility.mix verify.fullis the CI-equivalent local gate for maintainers.bash scripts/ci/accrue_host_uat.shis the repo-root wrapper around that same full contract.bash scripts/ci/accrue_host_hex_smoke.shis Hex smoke and stays separate from the checked-in host demo.mix accrue.installremains the production setup command for your own host app.