# AureaBilling OEM — Distribution Kit

Run **AureaBilling OEM** on your own infrastructure, invoicing through
**your own Stripe account** and licensed against Celestryll's Licentia
authority. This kit is everything you need to bring the stack up with
Docker: a `docker-compose.yml` that pulls Celestryll's published images,
an `.env` you fill in, and the steps below.

> **Prefer to let an AI agent do it?** If you use Claude Code (or a
> similar coding agent), point it at **[`CLAUDE-SETUP.md`](CLAUDE-SETUP.md)** —
> a runbook that automates most of this setup for you.

## What AureaBilling OEM is

AureaBilling is a multi-tenant, usage-aware billing microservice — "where
usage becomes revenue." You define a **brand catalog** of products and
prices; it creates customers and subscriptions, meters usage, and drives
invoicing **through your Stripe account**. It keeps a tamper-evident audit
chain (CDC-sourced) and exposes a REST API under `/api/v1`. It validates
its license against Celestryll on each request.

## Prerequisites

- **Docker** and **Docker Compose** (Docker Desktop, or Docker Engine +
  the `docker compose` plugin).
- A **Stripe account** — for a real deployment this is the biggest setup step.
  AureaBilling's invoicing runs through Stripe: you supply a **secret key** and
  set up a **webhook** so Stripe can notify AureaBilling of payment events. A
  Stripe **test-mode** account is fine for evaluation. *(Just kicking the tires?
  See **Demo mode** below — the `Simulated` gateway needs no Stripe account.)*
- A **Celestryll account and an AureaBilling license** — sign up at
  **https://celestryll.com/get-started**. A trial gives you a **Demo**
  license (30-day, online validation); purchasing upgrades you to a
  **Production** license. Either way you receive a **TenantId**,
  **LicenseKey**, and **ApiKey**.
- A **SQL Server or Azure SQL database** you control (the connection
  string is yours). No database of your own? See the optional bundled
  `sql` service in `docker-compose.yml` (fine for evaluation; use a
  managed, backed-up database for production). *(Demo mode uses an in-memory
  store and needs no database at all.)*
- A SQL client to apply the schema once — `sqlcmd` (mssql-tools18) for the
  bundled `apply-schema` helper, or Azure Data Studio / SSMS. *(Not needed in
  demo mode.)*

## Demo mode (evaluate with no Stripe and no database)

Want to see AureaBilling run before wiring up Stripe and SQL? The kit ships a
**demo profile** that needs **zero external secrets**. In `.env` set:

```
AUREABILLING__STORAGEPROVIDER=InMemory      # in-memory store — no database, no schema
AUREABILLING__GATEWAY=Simulated             # simulated payments — no Stripe account
AUREABILLING__LICENTIA__ENDPOINT=           # blank — license gate not registered
AUREABILLING_CONNECTION=                     # not needed with InMemory
AUREABILLING__STRIPE__SECRETKEY=             # not needed with Simulated
```

You still set three always-required values — any generated/dummy value works,
none are external secrets — plus a minimal catalog:

```
AUREABILLING_BROKER_APIKEY=<openssl rand -hex 32>
AUREABILLING__WEBHOOKS__SIGNINGSECRET=whsec_demo_placeholder
AUREABILLING__AUDIT__HMACKEY=<openssl rand -base64 48>
AUREABILLING__DEFAULTBRANDCODE=ACME
```

Author a one-brand catalog at `./catalog/aureabilling.catalog.json` (see
step 3 — with the Simulated gateway the `gatewayProductId` / `gatewayPriceId`
fields can be left blank or dummy). Then `docker compose up -d` and hit the
health endpoints (step 9). The smoke test (step 10) works too — the Simulated
gateway returns synthetic `cus_...` / `sub_...` ids. **Skip step 7 (schema)
entirely** in demo mode. Demo state is in-memory and resets when the container
restarts; use `SqlServer` + `Stripe` (the defaults) for anything real.

## Setup

### 1. Sign up and get your license

Create your account at **https://celestryll.com/get-started** and obtain
your **TenantId**, **LicenseKey**, and **ApiKey**. Keep them handy.

### 2. Configure `.env`

Copy the template and fill it in:

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

Edit `.env` (grouped by who supplies each value):

- **(a) From Celestryll:** `AUREABILLING__LICENTIA__TENANTID`,
  `AUREABILLING__LICENTIA__LICENSEKEY`, `AUREABILLING__LICENTIA__APIKEY`.
- **(b) Your infrastructure:** `AUREABILLING_CONNECTION` (your SQL
  connection string), `AUREABILLING__SCHEMANAME` (leave `aurea`),
  `AUREABILLING__STRIPE__SECRETKEY`, `AUREABILLING__WEBHOOKS__SIGNINGSECRET`
  (you get this in step 6), `AUREABILLING__DEFAULTBRANDCODE` (your brand,
  e.g. `ACME`), `AUREABILLING_BROKER_APIKEY` (your caller's API key), and
  `AUREABILLING_CATALOG_FILE` (path to your catalog, step 3).
- **(c) Secret to generate:** `AUREABILLING__AUDIT__HMACKEY` (step 5).
- **(d) Prefilled:** leave `AUREABILLING__LICENTIA__ENDPOINT` as
  `https://api.celestryll.com/licensing`.

`.env` is git-ignored. Never commit it or paste it into shared output.

**The broker API key (key-is-config-name convention).** AureaBilling
authenticates OEM/website calls with an API key sent in the `X-Api-Key`
header. Internally it is a `key -> brand` map: the **key** is the secret
the caller presents, the **value** is the brand that key is scoped to. The
compose file wires your value as:

```
AureaBilling__ApiKeys__${AUREABILLING_BROKER_APIKEY}=${AUREABILLING__DEFAULTBRANDCODE}
```

so the API key becomes part of an environment-variable **name**. Use an
env-name-safe token (letters, digits, underscores — **no** dashes or
dots). Generate one with `openssl rand -hex 32`. Need more than one key or
brand? Add extra `AureaBilling__ApiKeys__<key>=<BRAND>` lines to the
`aurea-api` service in `docker-compose.yml`.

### 3. Author your brand catalog

AureaBilling loads a **catalog JSON** (products and Stripe price/product
ids) from the path you set in `AUREABILLING_CATALOG_FILE`, mounted into the
container at `/config/catalog.json`. **You author your own** — do not use
Celestryll's. The shape is one or more brands, each with a list of
products:

```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"
        },
        {
          "productCode": "ACME_METERED",
          "displayName": "Acme Metered",
          "billingModel": "metered",
          "gatewayProductId": "prod_...",
          "gatewayPriceId": "price_...",
          "gatewayMeterEventName": "acme_units",
          "unitAmountCents": 500,
          "currency": "usd"
        }
      ]
    }
  ]
}
```

- `brandCode` must match your `AUREABILLING__DEFAULTBRANDCODE`.
- `billingModel` is `subscription` (flat/recurring) or `metered`
  (usage-based; needs a Stripe meter and `gatewayMeterEventName`).
- The `gatewayProductId` / `gatewayPriceId` values are **your** Stripe ids,
  created in your Stripe account (step 4). Leave `gatewayAccountId` blank
  for a standard single Stripe account.

Put your file at `./catalog/aureabilling.catalog.json` (the default) or set
`AUREABILLING_CATALOG_FILE` to wherever you keep it.

### 4. Create your Stripe products and prices

In your Stripe Dashboard (or via the Stripe CLI/API), create a Product and
Price for each catalog entry, then paste the resulting `prod_...` /
`price_...` ids into your catalog JSON. For **metered** products, create a
Stripe **meter** and use its event name as `gatewayMeterEventName`. Keep
your Stripe **secret key** for `.env` (`AUREABILLING__STRIPE__SECRETKEY`).

### 5. Generate the audit secret

One secret is yours to generate — it is **not** provided by Celestryll:

```bash
# Audit HMAC key (keys the tamper-evident audit chain)
openssl rand -base64 48
```

Put the value in `.env` as `AUREABILLING__AUDIT__HMACKEY`. Store a copy in
your secret manager — losing it means older audit rows can no longer be
re-verified.

### 6. Set up the Stripe webhook

AureaBilling receives Stripe events at **`/api/v1/stripe/webhook`**. In the
Stripe Dashboard → **Developers → Webhooks → Add endpoint**:

1. **Endpoint URL:** `https://<your-public-host>/api/v1/stripe/webhook`
   (Stripe must be able to reach it — use a public URL or a tunnel like the
   Stripe CLI's `stripe listen --forward-to` for local testing).
2. Subscribe to the billing events you use (invoice, subscription, and
   payment events — e.g. `invoice.paid`, `invoice.payment_failed`,
   `customer.subscription.updated`).
3. After creating it, copy the **Signing secret** (`whsec_...`) into `.env`
   as `AUREABILLING__WEBHOOKS__SIGNINGSECRET`.

The webhook path is exempt from the license gate and the API-key gate — it
is authenticated by Stripe's signature, verified against your signing
secret on every request.

### 7. Apply the database schema

> **Demo mode?** If you set `AUREABILLING__STORAGEPROVIDER=InMemory` (see
> "Demo mode" above), **skip this step entirely** — there is no database and
> nothing to apply. This step is only for the `SqlServer` storage provider.

AureaBilling's tables, stored procedures, and seed data are created by an
ordered set of SQL scripts, **shipped in this kit's [`./sql/`](sql/)
directory**. This kit includes an **apply-schema helper** that runs them in
order with the required settings — use it rather than hand-running each file:

```bash
# bash (Linux/macOS/WSL/Git Bash) — from the kit directory
./apply-schema.sh --database AureaBilling --schema aurea \
  --server localhost,1433 --user sa --password '<sa-password>'
#   add --demo on SQL Express to SKIP 02_CDC.sql (Express has no SQL Agent)
```

```powershell
# PowerShell (Windows)
./apply-schema.ps1 -Database AureaBilling -Schema aurea `
  -Server localhost,1433 -User sa -Password '<sa-password>'
#   add -Demo on SQL Express to skip 02_CDC.sql
```

The helper connects as an **admin/migrator** (`sa` or a DB owner), creates the
database if absent, and applies every `./sql/*.sql` in numeric order, passing
your `SchemaName` (default `aurea`). It always sets `QUOTED_IDENTIFIER ON`
(`-I`) and aborts on error (`-b`). The scripts are idempotent (safe to re-run
on upgrade). You need a `sqlcmd` on your PATH (mssql-tools18) or pass
`--sqlcmd <path>`.

- **`02_CDC.sql` needs SQL Server Agent.** The bundled `sql` service and any
  **SQL Express** engine have no Agent and cannot enable CDC, so applying
  `02_CDC.sql` errors there. On Express/demo, pass **`--demo`** (bash) /
  **`-Demo`** (PowerShell) to skip the CDC script — the rest applies cleanly.
  On full SQL Server / Azure SQL, enable Agent/CDC and omit the flag.
- **`sa` is only for this one-time schema apply.** Your app's **runtime**
  connection string (`AUREABILLING_CONNECTION`) must use the least-privilege
  `aurea_app` login that `08_AppSecurity.sql` creates (dev password
  `AureaBilling!App#Dev1`; set your own with `ALTER LOGIN`) — Production refuses
  an `sa` runtime connection at boot.
- **On Azure SQL, skip `08_AppSecurity.sql`.** It runs `CREATE LOGIN` (a
  server-level statement Azure SQL doesn't support). Apply the rest, then
  create a contained user for the app and grant it
  `SELECT/INSERT/UPDATE/DELETE/EXECUTE` on `SCHEMA::aurea` only (plus `SELECT`
  on the `cdc` schema if you enable CDC).
- The scripts are **idempotent** — safe to re-run when you upgrade.

### 8. Start the stack

```bash
docker compose up -d
```

This pulls Celestryll's published API image and starts it against your
database, your Stripe account, your catalog, and your license. (The usage
worker runs inside the API by default; for a scaled setup see the
`aurea-worker` service in `docker-compose.yml`.)

> **Fail-fast:** in Production the API refuses to start unless it has an API
> key (`AUREABILLING_BROKER_APIKEY`), a webhook signing secret, and an audit
> HMAC key — **always**. It additionally requires a SQL connection string
> **only** when `StorageProvider=SqlServer`, and a Stripe secret key **only**
> when `Gateway=Stripe` (the demo profile's `InMemory` + `Simulated` drop both
> of those). If the container exits immediately, check
> `docker compose logs aurea-api` — it names the missing value.

### 9. Verify health

```bash
curl -fsS http://localhost:8080/health      # {"status":"healthy","product":"AureaBilling OEM"}
curl -fsS http://localhost:8080/ready       # {"status":"ready"}
```

(Adjust the port if you changed `AUREABILLING_API_PORT`.)

### 10. Smoke test — create a customer and a subscription

Every API call is authenticated with your broker key in the `X-Api-Key`
header. Create a customer (tenant) for your brand, then subscribe it to a
catalog product:

```bash
KEY=<your AUREABILLING_BROKER_APIKEY>
BRAND=ACME    # your DefaultBrandCode

# Create a customer/tenant (also creates the Stripe customer).
curl -fsS -X POST http://localhost:8080/api/v1/customers/create \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d "{\"brandCode\":\"$BRAND\",\"tenantName\":\"Smoke Test Co\",\"tenantExternalId\":\"smoke-1\",\"email\":\"smoke@acme.example\"}"
# -> { "tenantId": "...", "gatewayCustomerId": "cus_..." }

TENANT=<tenantId from above>

# Subscribe that tenant to a catalog product.
curl -fsS -X POST http://localhost:8080/api/v1/subscriptions/create \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d "{\"tenantId\":\"$TENANT\",\"productCode\":\"ACME_PRO\"}"
# -> { "subscriptionId": "...", "gatewaySubscriptionId": "sub_...", "status": "..." }
```

A `gatewayCustomerId` (`cus_...`) and `gatewaySubscriptionId` (`sub_...`)
coming back means licensing validated, your Stripe key works, and your
catalog loaded end-to-end.

## Configuration reference

| `.env` variable | Who supplies it | Purpose |
|---|---|---|
| `AUREABILLING__LICENTIA__TENANTID` | Celestryll (sign-up) | Your Licentia tenant id |
| `AUREABILLING__LICENTIA__LICENSEKEY` | Celestryll (sign-up) | Your license key |
| `AUREABILLING__LICENTIA__APIKEY` | Celestryll (sign-up) | API key sent as `X-Licentia-ApiKey` |
| `AUREABILLING_CONNECTION` | You | Your SQL connection string |
| `AUREABILLING__SCHEMANAME` | You | SQL schema (default `aurea`) |
| `AUREABILLING__STRIPE__SECRETKEY` | You (Stripe) | Your Stripe secret key |
| `AUREABILLING__WEBHOOKS__SIGNINGSECRET` | You (Stripe) | Webhook signing secret (`whsec_...`) |
| `AUREABILLING__DEFAULTBRANDCODE` | You | Your brand code (e.g. `ACME`) |
| `AUREABILLING_BROKER_APIKEY` | You (generate) | Your caller's `X-Api-Key`; also the ApiKeys map name |
| `AUREABILLING_CATALOG_FILE` | You | Host path to your brand catalog JSON |
| `AUREABILLING__AUDIT__HMACKEY` | You (generate) | Keys the tamper-evident audit chain |
| `AUREABILLING__LICENTIA__ENDPOINT` | Prefilled | `https://api.celestryll.com/licensing` |
| `AUREABILLING__LICENTIA__FAILOPEN` | Prefilled | Deny (`false`) on Licentia outage |
| `AUREABILLING_VERSION` | Optional | Published image tag (default `latest`) |
| `AUREABILLING_API_PORT` | Optional | Host port for the API (default `8080`) |
| `AUREABILLING__RUNWORKERINAPI` | Optional | Run the usage worker in-API (`true`) |

## Upgrading

Pin a version in `.env` and bump it when Celestryll publishes a release:

```bash
# in .env
AUREABILLING_VERSION=1.4.0

docker compose pull
docker compose up -d
```

Re-apply the `sql/` scripts for the new version (they are idempotent) if
the release notes call for schema changes.

## Troubleshooting

- **Container exits on startup naming a missing value** — the Production
  fail-fast guard. Ensure `.env` has the broker API key, webhook signing
  secret, SQL connection string, Stripe secret key, and audit HMAC key.
  `docker compose logs aurea-api` names the one that's missing.
- **`402` / `LICENSE_REJECTED` on API calls** — the license didn't
  validate. Check `AUREABILLING__LICENTIA__LICENSEKEY`,
  `AUREABILLING__LICENTIA__APIKEY`, and `AUREABILLING__LICENTIA__TENANTID`
  match what Celestryll issued, and that
  `AUREABILLING__LICENTIA__ENDPOINT` is
  `https://api.celestryll.com/licensing`. A license binds to the first
  machine that validates it; if you moved hosts, contact support for a
  transfer.
- **`401` with `Missing or invalid X-Api-Key`** — your request's
  `X-Api-Key` doesn't match `AUREABILLING_BROKER_APIKEY`, or the key
  contains characters that broke the env-var name (use letters/digits/
  underscores only — regenerate with `openssl rand -hex 32`).
- **Stripe calls fail / customer create errors** — verify
  `AUREABILLING__STRIPE__SECRETKEY` is correct and for the right mode
  (test vs live), and that the `gatewayProductId`/`gatewayPriceId` in your
  catalog exist in that same Stripe account.
- **Webhook returns `401 invalid_signature`** — the `Stripe-Signature`
  didn't verify. Confirm `AUREABILLING__WEBHOOKS__SIGNINGSECRET` matches the
  signing secret of the exact webhook endpoint you created, and that your
  proxy forwards the **raw** request body unmodified.
- **API won't become ready / DB errors** — database connectivity. Verify
  `AUREABILLING_CONNECTION` (host, port, credentials, `Encrypt` /
  `TrustServerCertificate`), that the database exists, and that the schema
  scripts were applied. Check logs: `docker compose logs aurea-api`.
- **App can't log in to SQL** — confirm you used the least-privilege
  `aurea_app` user (not `sa`) in `AUREABILLING_CONNECTION` and granted it
  rights on `SCHEMA::aurea`.

## Support

- Get started / manage your license: **https://celestryll.com/get-started**
- Support: **support@celestryll.com**

When contacting support, include your **TenantId** and the API logs
(`docker compose logs aurea-api`). **Never** send your LicenseKey, ApiKey,
Stripe secret key, webhook signing secret, broker API key, or audit HMAC
key.
