# AureaBilling OEM — Automated Setup Runbook (for Claude Code)

You are an AI coding agent setting up **AureaBilling OEM** for a user, on
their machine, from this distribution kit. AureaBilling invoices through
the user's **own Stripe account** and validates its license against
Celestryll's Licentia. Follow these steps in order. Each step says whether
**YOU do it autonomously** or whether it **NEEDS THE USER**. Do not skip the
safety rules.

## Safety rules (read first)

- **Never ask the user to paste secrets into the chat / shared output.**
  License keys, DB passwords, Stripe keys, and the webhook signing secret
  go **directly into `.env`** by the user (or by you writing a value the
  user hands you privately) — never echoed back.
- **Generate the internal secret yourself** (the audit HMAC key, and — if
  the user wants — the broker API key) with strong randomness and write it
  straight to `.env`. **Do not print it** in your responses.
- **Never commit `.env`** or show its contents. The kit's `.gitignore`
  already excludes it — do not override that.
- Operate only in this `distribution/aureabilling/` directory.
- Treat the connection string, Stripe key, and webhook secret as secrets.
- **The Stripe webhook is created by the USER in the Stripe Dashboard** —
  you cannot (and must not try to) create it. You guide; they click.

## Step 1 — Verify Docker (YOU)

```bash
docker --version
docker compose version
docker info            # confirms the daemon is running
```

If any fail: tell the user to install Docker Desktop / start the Docker
daemon, then stop. Do not proceed without a running daemon.

## Step 2 — Collect the values only a human can provide (NEEDS THE USER)

Ask the user for these, which come from their Celestryll sign-up at
https://celestryll.com/get-started, their own database, and their Stripe
account. **Tell them to place these directly into `.env` themselves** (you
create `.env` in step 3 first), or to give them to you only if they are
comfortable — do **not** insist they paste secrets into the chat:

1. `AUREABILLING__LICENTIA__TENANTID` — from Celestryll.
2. `AUREABILLING__LICENTIA__LICENSEKEY` — from Celestryll.
3. `AUREABILLING__LICENTIA__APIKEY` — from Celestryll.
4. `AUREABILLING_CONNECTION` — their SQL Server / Azure SQL connection
   string (contains a password → secret).
5. `AUREABILLING__STRIPE__SECRETKEY` — their Stripe secret key.
6. `AUREABILLING__DEFAULTBRANDCODE` — their brand code (e.g. `ACME`).
7. Their brand **catalog JSON** (products/prices) — see step 4. They author
   it; you can help draft the shape but the Stripe ids are theirs.

The **webhook signing secret** (`AUREABILLING__WEBHOOKS__SIGNINGSECRET`)
comes later, in step 6, after the user creates the webhook in Stripe.

If the user has **no** database of their own, offer the optional bundled
`sql` service: have them uncomment it in `docker-compose.yml`, set
`MSSQL_SA_PASSWORD` + `AUREABILLING_APP_PASSWORD` and a matching
`AUREABILLING_CONNECTION` in `.env`. Note this is for evaluation; a
managed, backed-up database is better for production.

## Step 3 — Create and populate `.env` (YOU + USER)

> **Demo profile (no Stripe, no database).** If the user just wants to
> evaluate, set `AUREABILLING__STORAGEPROVIDER=InMemory` +
> `AUREABILLING__GATEWAY=Simulated` + blank `AUREABILLING__LICENTIA__ENDPOINT`
> in `.env`. Then `AUREABILLING_CONNECTION` and `AUREABILLING__STRIPE__SECRETKEY`
> can be blank (skip step 6's Stripe webhook and step 7's schema entirely). You
> still set the three always-required values — `AUREABILLING_BROKER_APIKEY`
> (step 3b), `AUREABILLING__WEBHOOKS__SIGNINGSECRET` (any `whsec_...`
> placeholder), `AUREABILLING__AUDIT__HMACKEY` (step 5) — and a minimal catalog
> (step 4; Simulated needs no real Stripe ids). For a real deployment leave the
> defaults `SqlServer` + `Stripe` and fill everything in. (G5)

Create `.env` from the template:

```bash
cp .env.template .env
```

Then fill it in:

- **You write** the prefilled + generated values (steps 3a, 5).
- **The user writes** (or privately hands you) the secrets from step 2 and
  the webhook secret from step 6. Prefer editing `.env` directly over
  echoing values.

### 3a. Confirm the prefilled values (YOU)

Ensure `.env` has:

```
AUREABILLING__LICENTIA__ENDPOINT=https://api.celestryll.com/licensing
AUREABILLING__LICENTIA__FAILOPEN=false
AUREABILLING__SCHEMANAME=aurea
AUREABILLING__RUNWORKERINAPI=true
```

### 3b. Set the broker API key (YOU, autonomously — env-name-safe)

The broker key is both the secret a caller presents in `X-Api-Key` AND the
config-name in the `ApiKeys` map (`AureaBilling__ApiKeys__<key>=<brand>`),
so it becomes part of an env-var NAME. Generate an env-name-safe token
(letters/digits/underscores only — NO dashes/dots) and write it to `.env`
as `AUREABILLING_BROKER_APIKEY` **without printing it**:

```bash
python - <<'PY'
import secrets, pathlib, re
env = pathlib.Path(".env"); text = env.read_text()
val = secrets.token_hex(32)                      # hex => env-name-safe
if re.search(r"(?m)^AUREABILLING_BROKER_APIKEY=", text):
    text = re.sub(r"(?m)^AUREABILLING_BROKER_APIKEY=.*$", f"AUREABILLING_BROKER_APIKEY={val}", text)
else:
    text += f"\nAUREABILLING_BROKER_APIKEY={val}\n"
env.write_text(text)
print("Broker API key written to .env (not shown).")
PY
```

Give the user the value **privately** (or tell them where it is) so their
caller can send it in `X-Api-Key` — do not print it in shared chat.

## Step 4 — Author the brand catalog (USER authors; YOU may draft) 

AureaBilling loads a catalog JSON from `AUREABILLING_CATALOG_FILE` (default
`./catalog/aureabilling.catalog.json`), mounted at `/config/catalog.json`.
**Do NOT ship Celestryll's catalog.** Help the user draft their own — one
or more brands, each with products — using this shape:

```json
{
  "brands": [
    {
      "brandCode": "ACME",
      "displayName": "Acme Billing",
      "gatewayAccountId": "",
      "supportEmail": "billing@acme.example",
      "defaultBillingCycle": { "interval": "monthly", "anchorDay": 1 },
      "billingGroups": [],
      "products": [
        { "productCode": "ACME_PRO", "displayName": "Acme Pro",
          "billingModel": "subscription",
          "gatewayProductId": "prod_...", "gatewayPriceId": "price_...",
          "unitAmountCents": 2900, "currency": "usd" }
      ]
    }
  ]
}
```

- `brandCode` must equal `AUREABILLING__DEFAULTBRANDCODE`.
- The `prod_...`/`price_...` ids are the USER's Stripe ids, created in THEIR
  Stripe account. If they haven't created them yet, guide them to (Stripe
  Dashboard → Products), then paste the ids into the catalog. Metered
  products also need a Stripe meter + `gatewayMeterEventName`.

## Step 5 — Generate the audit secret (YOU, autonomously)

Generate one strong random value and write it into `.env`. **Do not print
it.**

```bash
python - <<'PY'
import secrets, pathlib, re
env = pathlib.Path(".env"); text = env.read_text()
val = secrets.token_urlsafe(48)                  # ~64 chars, >32 bytes entropy
key = "AUREABILLING__AUDIT__HMACKEY"
if re.search(rf"(?m)^{key}=", text):
    text = re.sub(rf"(?m)^{key}=.*$", f"{key}={val}", text)
else:
    text += f"\n{key}={val}\n"
env.write_text(text)
print("Audit HMAC key written to .env (not shown).")
PY
```

(If Python isn't available, use `openssl rand -base64 48` and write it into
`.env` with an editor — still without printing the value.)

Remind the user to copy this into their secret manager; losing the audit
HMAC key means past audit rows can't be re-verified.

## Step 6 — Set up the Stripe webhook (NEEDS THE USER — dashboard step)

**This is a user-in-Stripe-Dashboard step. You cannot create the webhook.**
Guide the user to:

1. Stripe Dashboard → **Developers → Webhooks → Add endpoint**.
2. **Endpoint URL:** `https://<their-public-host>/api/v1/stripe/webhook`
   (Stripe must reach it publicly; for local testing suggest the Stripe CLI
   `stripe listen --forward-to localhost:8080/api/v1/stripe/webhook`).
3. Subscribe to the billing events they use (invoice/subscription/payment,
   e.g. `invoice.paid`, `invoice.payment_failed`,
   `customer.subscription.updated`).
4. Copy the endpoint's **Signing secret** (`whsec_...`).

Have the user put that value into `.env` as
`AUREABILLING__WEBHOOKS__SIGNINGSECRET` (or hand it to you privately to
write). The API will not start in Production without it.

## Step 7 — Apply the database schema (YOU, with the admin credential)

> **Demo mode?** If the user chose the demo profile
> (`AUREABILLING__STORAGEPROVIDER=InMemory`), **skip this whole step** — there
> is no database and nothing to apply. This step is only for `SqlServer`
> storage.

The schema is **shipped in this kit's `./sql/` directory**. Apply it with the
bundled **apply-schema helper** — do **not** hand-run raw `sqlcmd -i` (scripts
that create filtered indexes need `SET QUOTED_IDENTIFIER ON`, which plain
`sqlcmd` leaves OFF; the helper's `-I` fixes this). Run as a DB admin/migrator
(`sa` / owner); it creates `AureaBilling` if absent and passes `SchemaName`
(default `aurea`):

```bash
./apply-schema.sh --database AureaBilling --schema aurea \
  --server <server> --user sa --password '<sa-password>'
#   add --demo on SQL Express to SKIP 02_CDC.sql (Express has no SQL Agent)
```

```powershell
./apply-schema.ps1 -Database AureaBilling -Schema aurea `
  -Server <server> -User sa -Password '<sa-password>'   # add -Demo on Express
```

Needs `sqlcmd` on PATH (mssql-tools18) or `--sqlcmd <path>`.

Decision points:

- **SQL Express / bundled `sql`?** `02_CDC.sql` needs SQL Server Agent, which
  Express has not — pass `--demo` (bash) / `-Demo` (PowerShell) to skip it; the
  rest applies cleanly. On full SQL Server / Azure SQL, enable Agent/CDC and
  omit the flag.
- **Azure SQL?** SKIP `08_AppSecurity.sql` (it runs `CREATE LOGIN`, which
  Azure SQL rejects) — move it out of `./sql/` first, then create a contained
  user and grant it `SELECT/INSERT/UPDATE/DELETE/EXECUTE` on `SCHEMA::aurea`
  only (plus `SELECT` on `cdc` if CDC is enabled).
- **Self-hosted SQL Server?** `08_AppSecurity.sql` creates the least-privilege
  `aurea_app` login (dev password `AureaBilling!App#Dev1`); set a strong
  password (`ALTER LOGIN`) and use that (**not** `sa`) in
  `AUREABILLING_CONNECTION` — Production refuses an `sa` runtime connection.
- Scripts are idempotent — safe to re-run.
- `sa` is the **admin/migrator** credential for schema apply only; the **app**
  credential in `AUREABILLING_CONNECTION` is the runtime one (`aurea_app`). Ask
  the user for the admin credential if you don't have it — keep it out of chat.

## Step 8 — Bring up the stack (YOU)

```bash
docker compose pull
docker compose up -d
docker compose ps
```

If `aurea-api` exits immediately, read logs: `docker compose logs
aurea-api`. In Production it fail-fasts naming any missing required value
(broker API key, webhook secret, connection string, Stripe key, audit key).

## Step 9 — Poll health until ready (YOU)

Use the port from `AUREABILLING_API_PORT` (default 8080):

```bash
for i in $(seq 1 30); do
  curl -fsS http://localhost:8080/health >/dev/null 2>&1 && break
  sleep 2
done
curl -fsS http://localhost:8080/health      # {"status":"healthy",...}
curl -fsS http://localhost:8080/ready       # {"status":"ready"}
```

If health never comes up, it's almost always a missing `.env` value (see
step 8 logs), `AUREABILLING_CONNECTION`, or an un-applied schema.

## Step 10 — Smoke test: create a customer + subscription (YOU, autonomously)

Read the broker key from `.env` in-process and call the API with it in
`X-Api-Key` — **do not print the key**. Create a customer, then subscribe
it to a product that exists in the user's catalog.

```bash
python - <<'PY'
import os, re, json, pathlib, urllib.request
env = dict(re.findall(r"(?m)^([A-Z0-9_]+)=(.*)$", pathlib.Path(".env").read_text()))
key   = env["AUREABILLING_BROKER_APIKEY"]
brand = env.get("AUREABILLING__DEFAULTBRANDCODE", "ACME")
port  = env.get("AUREABILLING_API_PORT", "8080")
def post(path, body):
    req = urllib.request.Request(f"http://localhost:{port}{path}", data=json.dumps(body).encode(),
        method="POST", headers={"Content-Type":"application/json","X-Api-Key":key})
    return json.load(urllib.request.urlopen(req))
cust = post("/api/v1/customers/create",
    {"brandCode":brand,"tenantName":"Smoke Test Co","tenantExternalId":"smoke-1","email":"smoke@example.test"})
print("customer:", cust.get("tenantId"), cust.get("gatewayCustomerId"))
# Replace ACME_PRO with a productCode from the user's catalog:
sub = post("/api/v1/subscriptions/create", {"tenantId":cust["tenantId"],"productCode":"ACME_PRO"})
print("subscription:", sub.get("gatewaySubscriptionId"), sub.get("status"))
PY
```

A `cus_...` / `sub_...` coming back means licensing validated, the Stripe
key works, and the catalog loaded end-to-end. (`402`/`LICENSE_REJECTED` =
Licentia values wrong, go back to step 2/3. `401` with an X-Api-Key message
= the key/header mismatch, re-check step 3b. A Stripe error = check the
secret key and that the catalog `productCode`/ids exist in that account.)

## Step 11 — Report success (YOU)

Summarize for the user, **without printing any secret**:

- Docker services running (`docker compose ps`).
- Health: `/health` + `/ready` OK.
- Smoke test created a `cus_...` and `sub_...`.
- API base URL (e.g. `http://localhost:8080`) and where to change the port.
- Reminders: `.env` holds secrets and is git-ignored; copy the audit key
  (and broker key) into their secret manager; the Stripe webhook is live at
  their public `/api/v1/stripe/webhook`; pin `AUREABILLING_VERSION` for
  reproducible upgrades.

## Autonomous vs. user-provided — quick map

| Step | You (autonomous) | Needs the user |
|---|---|---|
| 1 Verify Docker | yes | install/start Docker if missing |
| 2 Collect license + DB + Stripe key + brand | — | yes TenantId, LicenseKey, ApiKey, connection string, Stripe secret key, brand code |
| 3 Create `.env` + broker key | yes create, prefilled, generate broker key | yes writes the step-2 secrets into `.env` |
| 4 Author brand catalog | yes draft the shape | yes owns Stripe product/price ids |
| 5 Generate audit HMAC key | yes generate + write, never print | — |
| 6 Create Stripe webhook | guide only | yes creates it in Stripe Dashboard; supplies `whsec_...` |
| 7 Apply DB schema | yes run scripts | yes provide admin DB credential |
| 8 `docker compose up -d` | yes | — |
| 9 Poll health | yes | — |
| 10 Smoke test | yes create customer + subscription | — |
| 11 Report success | yes | — |
