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

# Core concepts

> Understand products, plans, customers, paywall checks, and usage tracking.

## Table of contents

* [Products and plans](#products-and-plans)
* [Plan labels](#plan-labels)
* [Meters](#meters)
* [Customer references](#customer-references)
* [Paywall protection flow](#paywall-protection-flow)
* [Purchase lifecycle](#purchase-lifecycle)
* [Authentication flow](#authentication-flow)
* [Usage tracking](#usage-tracking)
* [Next steps](#next-steps)

## Products and plans

### Products

A product represents the API, app, or MCP server you monetize.

* Product reference (`prd_...`)
* Name and description

Products are created in SolvaPay Console and are available for SDK integration by default.

```typescript theme={null}
const payable = solvaPay.payable({
  product: 'prd_myapi',
})
```

### Plans

A plan defines pricing and access for a product.

* Plan reference (`pln_...`)
* Type: `recurring`, `usage-based`, `one-time`, or `hybrid`
* Price and currency
* Features
* Status: `active`, `inactive`, or `archived`

Plans are configured as part of product setup. Customers select a plan during activation; the SDK resolves the correct plan from the customer's purchase automatically.

### Product-plan relationship

* One product can have multiple plans
* Plans are embedded in products
* One default plan per product
* Paywall checks verify plan access for a product

## Plan labels

A plan is an `options[]` list. These names are **derived labels**, not a `type` you send:

| Label       | Derived when              | Best for                          |
| ----------- | ------------------------- | --------------------------------- |
| Recurring   | Billing cycle, no meter   | SaaS subscriptions                |
| Usage-based | Metered, no billing cycle | APIs and token services           |
| Hybrid      | Billing cycle and a meter | Mixed workloads                   |
| One-time    | Flat charge, no cycle     | Digital goods and lifetime access |

See [Plans](/plans/overview) to compose those options.

## Meters

Meters define what usage to measure.

* Seeded default: `requests` (`name` / `displayName` / `unit`)
* MCP tool meters: `tool:{toolName}`
* Custom meters for business-specific usage

Plans can enforce limits using meter data. Copy uses the meter's `unit` noun — see [Plans and billing glossary](/plans/glossary).

## Customer references

A customer reference (`customerRef`) maps your user identity to SolvaPay.

```typescript theme={null}
const customerRef = await solvaPay.ensureCustomer('user_123', 'user_123', {
  email: 'user@example.com',
  name: 'Jane Doe',
})
```

For HTTP adapters, pass customer identity in headers or auth context.

```typescript theme={null}
app.post('/tasks', payable.http(async req => {
  const customerRef = req.headers['x-customer-ref']
  return { ok: true, customerRef }
}))
```

### Create-or-link semantics

When you call `POST /v1/sdk/customers` (or `ensureCustomer` / `syncCustomer`) with an email that already exists under this provider and the caller supplies an `externalRef`, the API links the `externalRef` onto the existing customer instead of returning `409 Conflict`. This lets you idempotently adopt pre-existing SolvaPay customers when wiring up your own auth layer for the first time. Callers without `externalRef` still get `409` on duplicate email so the behavior is explicit.

### Update a customer

Use `PATCH /v1/sdk/customers/:reference` to backfill or change `externalRef`, `name`, or `email`. Only the fields you supply are modified.

```typescript theme={null}
await fetch(`https://api.solvapay.com/v1/sdk/customers/${ref}`, {
  method: 'PATCH',
  headers: { Authorization: `Bearer ${process.env.SOLVAPAY_SECRET_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ externalRef: 'auth_user_12345' }),
})
```

## Data helpers

Three read-only endpoints power customer-facing UI without bloating `checkPurchase`:

* **`GET /v1/sdk/platform-config`** — browser-safe platform values (today: the SolvaPay Stripe publishable key used to initialize the Stripe.js client). Resolves against the sandbox or live environment matching your secret key. Call it once on page load before mounting `<PaymentForm>`; additions will land here rather than in `/sdk/merchant`.
* **`GET /v1/sdk/merchant`** — provider identity safe to render in checkout and mandate copy: `name`, `legalName`, `supportContact`, `termsUrl`, `privacyUrl`, `iconUrl` (square app icon), and `logoUrl` (absolute URL resolved against `ASSETS_BASE_URL`). Used by SDK components like `MandateText` and `CheckoutSummary`.
* **`GET /v1/sdk/payment-method?customerRef=…`** — returns `{ kind: 'card', brand, last4, expMonth, expYear, reusable }` or `{ kind: 'none' }`. The card brand and last4 are mirrored onto the Customer by the `payment_intent.succeeded` webhook, so this endpoint reads from SolvaPay's database with no Stripe round-trip. `reusable` distinguishes a chargeable saved card from a card that is only on file. Safe to poll alongside `checkPurchase`.

All three are exposed as route-wrapper helpers in `@solvapay/next` (`getMerchant`, `getProduct`, `getPaymentMethod`) and as fetch handlers in `@solvapay/server/fetch`. In an MCP server they are not separate tools — `createSolvaPayMcpServer` delivers the same data on the `account` viewer's bootstrap payload, which seeds the client caches at mount.

## Paywall protection flow

When your handler is wrapped with `payable()`, the SDK:

1. Resolves customer reference
2. Checks purchase status
3. Checks usage limits
4. Executes business logic when allowed
5. Tracks usage
6. Returns a paywall response when blocked

### Example blocked response

```typescript theme={null}
{
  error: 'PaywallError',
  message: 'Purchase required',
  checkoutUrl: 'https://customer.solvapay.com/...',
}
```

## Purchase lifecycle

Purchase states:

* **Active** — customer has access
* **Pending cancellation** — active with `cancelledAt` set; access continues until period end, can be reactivated
* **Cancelled** — cancel confirmed at period end
* **Expired** — purchase ended (period elapsed or plan switch)
* **Past due** — payment failed; may be suspended

### Cancel and reactivate renewal

```typescript theme={null}
import { cancelRenewal, reactivateRenewal } from '@solvapay/next'

// Cancel auto-renewal (access continues until period end)
await cancelRenewal(request, { purchaseRef: 'pur_...', reason: 'Too expensive' })

// Undo cancellation while still in active period
await reactivateRenewal(request, { purchaseRef: 'pur_...' })
```

### Activate a plan directly

Activate a product for a customer on a specific plan without checkout. Handles free plans, credit-based activation, usage-based (PAYG) plans, and plan switching.

```typescript theme={null}
import { activatePlan } from '@solvapay/next'

const result = await activatePlan(request, {
  productRef: 'prd_...',
  planRef: 'pln_...',
})
// result.status: 'activated' | 'already_active' | 'topup_required' | 'payment_required' | 'invalid'
```

Activation policy by plan type:

* **Free** — always `activated`.
* **Usage-based (metered, no billing cycle)** — `activated` immediately. A zero balance is not an activation gate. Empty-wallet access is enforced later as `topup_required` at usage time.
* **Paid recurring, hybrid, or one-time** — `payment_required`. Complete checkout before the plan activates.

When the customer already has an active purchase on a different plan, `activatePlan` automatically expires the old purchase and creates a new one.

See [Purchase lifecycle management](/sdks/typescript/guides/purchase-management) for the full guide.

## Authentication flow

You keep your existing auth and map identities to SolvaPay customers.

Common pattern:

1. Extract user ID from your auth layer
2. Use ID as `customerRef`
3. Sync email/name as needed
4. Run purchase checks

## Usage tracking

Use `payable()` for automatic usage tracking, or record usage directly.

```typescript theme={null}
await solvaPay.trackUsage({
  customerRef: 'user_123',
  actionType: 'api_call',
  units: 1,
  outcome: 'success',
  productRef: 'prd_P9Q0R1S2',
})
```

Usage-based and hybrid plans enforce limits from meter data.

## Next steps

* [Customer linkage (bring your own client and IdP)](/sdks/typescript/guides/customer-linkage) — `ensureCustomer(externalRef)` from any backend

* [Quick start](./quick-start)

* [CLI setup](./cli)

* [Installation](./installation)

* [Plans overview](/plans/overview)

* [Plans and billing glossary](/plans/glossary)
