> ## Documentation Index
> Fetch the complete documentation index at: https://docs.creem.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Concepts

> How the Creem Convex component models billing: the entity that owns it, the four sources of billing state, and the API contract the widgets consume.

Three ideas explain most of the component's behaviour. Read this once and the
rest of the documentation stops holding surprises.

## The billing entity

Every subscription, order, and credit balance belongs to one billing entity, identified by the `entityId` your resolver returns. It is the first decision you make and the most awkward one to change later.

For personal billing, return the user ID. For organization or team billing, return the org ID. Checkout metadata and webhook resolution follow automatically, with no other code changes:

```ts theme={null}
const resolve: ApiResolver = async (ctx) => {
  const identity = await ctx.auth.getUserIdentity();
  if (!identity) return null; // anonymous; public pricing pages still render
  const org = await ctx.runQuery(api.orgs.getActiveOrg);
  return {
    userId: identity.subject,
    email: identity.email!,
    entityId: org?._id ?? identity.subject,
  };
};
```

<Warning>
  Return `null` for an unauthenticated caller. Anything **thrown** from the resolver is treated as a
  real failure: it is logged and rethrown instead of quietly degrading a signed-in user to the
  logged-out pricing page. (Throwing the exported `CreemNotAuthenticatedError` is also accepted as
  an anonymous signal.)
</Warning>

By default, the component tracks the entity's active app-owned plan itself. Apps that own their plan assignment can override that from the resolver — see [Advanced → Resolver plan overrides](/code/sdks/convex/advanced#resolver-plan-overrides).

## The billing state model

Billing state comes from four places. Three of them store data. The fourth is derived from the other three, and that distinction is worth learning early:

| Source                   | Owned by                 | Holds                                                                                         | Read it from                    |
| ------------------------ | ------------------------ | --------------------------------------------------------------------------------------------- | ------------------------------- |
| **Creem subscriptions**  | Creem, synced by webhook | Recurring subscriptions. An entity can hold several at once, such as a base plan plus add-ons | `snapshot.subscriptions[]`      |
| **Creem orders**         | Creem, synced by webhook | One-time purchases. Subscription checkouts create orders too                                  | `snapshot.orders[]`             |
| **App-plan assignments** | This component           | Free plans, no-card trials, and custom internal plans, both current and scheduled             | `snapshot.appPlanAssignments[]` |
| **`access`**             | Derived, not stored      | Everything the entity currently has, flattened across the three above                         | `snapshot.access[]`             |

Write to the first three through checkout, webhooks, and `creem.appPlans.activate`. Do not treat `access` as a source of truth. It is recomputed on every read.

```ts theme={null}
{
  entityId: "org_123",
  subscriptions: [/* Creem recurring subscriptions, incl. add-ons */],
  orders: [/* one-time orders */],
  appPlanAssignments: [/* app-owned plans: free, no-card trials */],
  access: [/* derived: everything the entity currently has */],
  paymentRecoveryState: "none",
  availableBillingActions: ["portal", "cancel"],
}
```

`snapshot` (generated by `creem.api({ resolve })`) and `creem.getBillingSnapshot(ctx, { entityId })` both return this shape. Everything is synced into Convex, so reading it is a local reactive query with no Creem API round-trip.

### Easily confused pairs

| Pair                                        | The distinction                                                                                                                                                                                                                                                                                                                |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `updateBehavior` vs `appPlanUpdateBehavior` | `updateBehavior` covers paid-to-paid switches and unit changes, using Creem proration or `"period-end"`. `appPlanUpdateBehavior` covers paid-to-app-owned, which is a cancellation flow, so it takes `"period-end"` or `"immediate"`.                                                                                          |
| `groupId` vs `eligibilityScopeId`           | `groupId` is presentation: which audience tab a plan appears under. `eligibilityScopeId` is entitlement: which plans count as mutually exclusive alternatives for scoped trial expiry. A plan can use both, independently.                                                                                                     |
| Creem-managed trial vs app-owned trial      | A Creem-managed trial is a `category: "paid"` recurring plan with a trial configured on the Creem product. Creem owns the card, the subscription, and `subscription.trialing`. An app-owned trial is `category: "trial"` with `billingType: "custom"`. No Creem subscription exists, and the component records the activation. |

## The connected API contract

Widgets hold no Convex function references of their own. Every backend call goes through one `ConnectedBillingApi` object they read from the provider, and its fields decide what the UI can do. Export and wire only what your product should allow.

`connectCreemApi` maps the conventional `convex/billing.ts` export names onto that shape for you. Every reference is typed by function kind, args, **and** return type, so a missing or mis-wired export is a compile error rather than a blank widget:

```ts theme={null}
import { connectCreemApi } from "@creem_io/convex/react"; // or /svelte
import { api } from "../convex/_generated/api";

const billingApi = connectCreemApi(api.billing);
```

| API field                             | Generated export name                | Used by                                  | If omitted                            |
| ------------------------------------- | ------------------------------------ | ---------------------------------------- | ------------------------------------- |
| `uiModel`                             | `uiModel`                            | All connected widgets                    | Required; widgets cannot load state   |
| `checkouts.create`                    | `checkoutsCreate`                    | `Subscription.Root`, `Product.Root`      | Required; no checkout is possible     |
| `subscriptions.update`                | `subscriptionsUpdate`                | Plan switches, unit changes              | Switch and unit controls hidden       |
| `subscriptions.cancel`                | `subscriptionsCancel`                | Cancel buttons                           | Cancel controls hidden                |
| `subscriptions.resume`                | `subscriptionsResume`                | Undo scheduled cancellation              | Resume controls hidden                |
| `subscriptions.cancelScheduledUpdate` | `subscriptionsCancelScheduledUpdate` | App-side period-end update undo          | Pending update undo hidden            |
| `plans.activate`                      | `plansActivate`                      | App-owned plans (free, no-card trials)   | Trial/free plan cards cannot activate |
| `customers.portalUrl`                 | `customersPortalUrl`                 | `BillingPortal`, payment recovery button | Portal buttons hidden                 |
| `transactions.search`                 | `transactionsSearch`                 | `BillingHistory`                         | Billing history can't render          |
| `credits.getBalance`                  | `creditsGetBalance`                  | `Credits.Root`                           | Credit balance UI can't work          |

Building the object by hand still works when you wrap the generated functions in your own RBAC actions. See [Advanced → Custom auth and RBAC](/code/sdks/convex/advanced#custom-auth-and-rbac).

## Where to go next

* Wire the entity and export the API: [Quickstart](/code/sdks/convex/quickstart)
* Enforce access on the server: [Entitlements](/code/sdks/convex/entitlements)
* Custom auth, RBAC, and webhook middleware: [Advanced](/code/sdks/convex/advanced)
* Exact signatures and props: [Reference](/code/sdks/convex/reference)
