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

# Setup

> Enable Payments, connect a payment account, and understand how the pieces fit together

export const PaymentsConcepts = () => {
  const badge = (text, color) => {
    const colors = {
      zinc: "bg-zinc-200 text-zinc-700 dark:bg-zinc-700 dark:text-zinc-200",
      violet: "bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-400",
      amber: "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400",
      emerald: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400",
      sky: "bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-400"
    };
    return <span className={"inline-block rounded-full px-2 py-0.5 text-[11px] font-medium " + (colors[color] || colors.zinc)}>
        {text}
      </span>;
  };
  const itemPill = (qty, name) => <div className="flex items-center gap-1.5 rounded-md border border-emerald-200 bg-emerald-50 px-2 py-1 dark:border-emerald-800 dark:bg-emerald-950/40">
      <div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
      <span className="text-[11px] font-medium text-emerald-700 dark:text-emerald-400">{qty} {name}</span>
    </div>;
  const priceTag = (amount, interval) => <div className="flex items-baseline gap-1">
      <span className="text-[15px] font-semibold text-zinc-900 dark:text-white">{amount}</span>
      {interval ? <span className="text-[11px] text-zinc-500 dark:text-zinc-500">/ {interval}</span> : null}
    </div>;
  const productCard = (name, price, interval, items, isHighlight, badgeText, badgeColor) => <div className={isHighlight ? "flex flex-col gap-2 rounded-xl border border-violet-300 bg-violet-50/50 p-3 dark:border-violet-700 dark:bg-violet-950/20" : "flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900"}>
      <div className="flex items-center justify-between gap-2">
        <span className="text-[13px] font-semibold text-zinc-900 dark:text-white">{name}</span>
        {badgeText ? badge(badgeText, badgeColor) : null}
      </div>
      {priceTag(price, interval)}
      {items.length > 0 ? <div className="flex flex-wrap gap-1.5 pt-0.5">
          {items.map((item, i) => <span key={i}>{itemPill(item[0], item[1])}</span>)}
        </div> : null}
    </div>;
  const customerRow = (type, label, items, productName) => <div className="flex items-center gap-3 rounded-lg border border-zinc-200 bg-white px-3 py-2 dark:border-zinc-700 dark:bg-zinc-900">
      <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-sky-100 text-[11px] font-bold text-sky-700 dark:bg-sky-900/40 dark:text-sky-400">
        {type === "user" ? "U" : "T"}
      </div>
      <div className="flex flex-1 flex-col gap-0.5">
        <span className="text-[12px] font-medium text-zinc-700 dark:text-zinc-300">{label}</span>
        <div className="flex items-center gap-3">
          {items.map((item, i) => <span key={i} className="text-[11px] text-zinc-500 dark:text-zinc-500">
              {item[0]}: <span className="font-semibold text-zinc-700 dark:text-zinc-300">{item[1]}</span>
            </span>)}
        </div>
      </div>
      {badge(productName, "violet")}
    </div>;
  return <div className="not-prose my-6 space-y-4">
      <div className="overflow-hidden rounded-2xl border border-zinc-950/10 dark:border-white/10">
        <div className="flex items-center justify-between border-b border-zinc-950/10 bg-zinc-950/[0.03] px-4 py-2.5 dark:border-white/10 dark:bg-white/[0.03]">
          <div className="flex items-center gap-2">
            <span className="text-[13px] font-semibold text-zinc-800 dark:text-zinc-200">Product Line</span>
            {badge("mutually exclusive", "zinc")}
          </div>
          <span className="text-[11px] text-zinc-500 dark:text-zinc-500">"Plan"</span>
        </div>
        <div className="grid gap-2.5 p-3 sm:grid-cols-3">
          {productCard("Free", "$0", null, [["10", "credits"]], false, null, null)}
          {productCard("Pro", "$20", "mo", [["500", "credits"], ["5", "seats"]], true, "popular", "violet")}
          {productCard("Enterprise", "$99", "mo", [["5,000", "credits"], ["50", "seats"]], false, null, null)}
        </div>
      </div>

      <div className="grid gap-2.5 sm:grid-cols-2">
        <div className="overflow-hidden rounded-2xl border border-zinc-950/10 dark:border-white/10">
          <div className="flex items-center justify-between border-b border-zinc-950/10 bg-zinc-950/[0.03] px-4 py-2.5 dark:border-white/10 dark:bg-white/[0.03]">
            <div className="flex items-center gap-2">
              <span className="text-[13px] font-semibold text-zinc-800 dark:text-zinc-200">Standalone Product</span>
              {badge("stackable", "amber")}
            </div>
          </div>
          <div className="p-3">
            {productCard("Credit Pack", "$5", null, [["100", "credits"]], false, "one-time", "amber")}
          </div>
        </div>
        <div className="overflow-hidden rounded-2xl border border-zinc-950/10 dark:border-white/10">
          <div className="flex items-center justify-between border-b border-zinc-950/10 bg-zinc-950/[0.03] px-4 py-2.5 dark:border-white/10 dark:bg-white/[0.03]">
            <div className="flex items-center gap-2">
              <span className="text-[13px] font-semibold text-zinc-800 dark:text-zinc-200">Add-on</span>
              {badge("requires Pro", "sky")}
            </div>
          </div>
          <div className="p-3">
            {productCard("Priority Support", "$10", "mo", [], false, "subscription", "sky")}
          </div>
        </div>
      </div>

      <div className="overflow-hidden rounded-2xl border border-zinc-950/10 dark:border-white/10">
        <div className="border-b border-zinc-950/10 bg-zinc-950/[0.03] px-4 py-2.5 dark:border-white/10 dark:bg-white/[0.03]">
          <span className="text-[13px] font-semibold text-zinc-800 dark:text-zinc-200">Customers</span>
          <span className="ml-2 text-[11px] text-zinc-500 dark:text-zinc-500">item balances after purchase</span>
        </div>
        <div className="space-y-2 p-3">
          {customerRow("user", "alice@example.com", [["credits", "487"], ["seats", "5"]], "Pro")}
          {customerRow("team", "Acme Corp", [["credits", "4,832"], ["seats", "50"]], "Enterprise")}
        </div>
      </div>
    </div>;
};

Hexclave includes a Payments app that handles billing, subscriptions, and one-time purchases. Instead of building your own billing system, you define products in the dashboard and Hexclave takes care of checkout, entitlement tracking, subscription lifecycle, and invoicing.

## Getting started

<Steps>
  <Step title="Enable Payments">
    Go to the **Apps** section in your dashboard, find **Payments**, and enable it.
  </Step>

  <Step title="Connect your payment account">
    Open **Payments -> Settings** and follow the onboarding flow. You'll be asked for business details, bank info, and identity verification. Once approved, payments are live.
  </Step>

  <Step title="Turn on test mode">
    While building, enable **test mode** in **Payments -> Settings**. All purchases will be free - no real money is charged. You can switch to live when you're ready.
  </Step>
</Steps>

<Info>
  Hexclave Payments is currently only available for US-based businesses, and processes payments in **USD**. Support for other countries and currencies is coming soon.
</Info>

## Core concepts

Before writing any code, it helps to understand how the pieces fit together.

A **product** is something you sell - a subscription plan, a one-time purchase, or a credit pack. Products can have one or more **prices** (one-time or recurring), and they can include **items** - quantifiable entitlements like credits, seats, or API calls.

A **product line** groups products that are mutually exclusive. For example, a "Plan" product line might contain Free, Pro, and Enterprise - a customer can only have one at a time. When they upgrade, the old plan is replaced.

A **customer** is whoever owns the purchase. This can be a user, a team, or a custom external entity.

Here's how these pieces look in practice:

<PaymentsConcepts />

And the typical flow to make it all work:

1. You define products and items in the dashboard
2. Your app generates a checkout URL and redirects the user to the hosted checkout page
3. The user pays, Hexclave is notified, and the product is granted
4. Your app reads the customer's products and item balances to control access

Each step has its own guide:

* [Products & Pricing](./products-and-pricing) - define what you sell
* [Items & Entitlements](./items-and-entitlements) - credits, seats, and quota
* [Checkout & Purchases](./checkout) - sell a product and read what a customer owns
* [Subscriptions](./subscriptions) - switching plans and cancellation
* [Billing & Invoices](./billing-and-invoices) - payment methods and billing history
* [Refunds](./refunds) - partial or full refunds, and ending access
* [Granting Products](./granting-products) - give access without checkout
* [Customer Types](./customers) - users, teams, and custom customers

## Test mode

While you're building, turn on **test mode** in **Payments -> Settings**. With test mode on, purchases skip the payment processor entirely: products and items are granted instantly, no card is collected, and no money moves. It's on by default in development environments.

This lets you wire up checkout, entitlements, and subscription logic end to end without touching real money. When you're confident everything works, turn test mode off - at which point purchases go through the payment processor and **charge real money**.

Test mode does **not** simulate every live behavior:

* **Free trials are skipped.** The product is granted immediately; there is no trial period and no deferred first charge. Turn test mode off to exercise the real trial checkout.
* **Catalog edits still apply to production.** Test mode only changes how purchases run, not which products exist.
* **Money refunds are not available.** You can still end access from a refund dialog; you cannot return money that was never charged. See [Refunds](./refunds).

<Warning>
  There's no "live but free" middle ground. With test mode **off**, every purchase is a real, billable charge. Keep test mode on until you're ready to take real payments.
</Warning>

## Dashboard

The dashboard gives you full visibility and control over your payments:

* **Product Lines** - Group products into mutually exclusive tiers. In **Payments -> Product Lines**.
* **Products & Items** - Create and edit products, set pricing, and configure included items. In **Payments -> Products & Items**.
* **Customers** - View item balances per customer, manually adjust quantities, and create checkout URLs. In **Payments -> Customers**. Granting a product without checkout is [SDK/API only](./granting-products).
* **Transactions** - See all payment activity, filter by type and customer, export CSV, and issue [refunds](./refunds) on purchase rows. In **Payments -> Transactions**.
* **Payouts** - View payout information. In **Payments -> Payouts**. Unavailable in development environments.
* **Settings** - Connect your payment account, toggle test mode, configure payment methods, and **block new purchases** (existing subscriptions keep renewing). In **Payments -> Settings**.

### Payment emails

Email notifications are sent automatically on payment events:

* **Payment Receipt** - Sent on successful payment with product details, amount, and receipt link
* **Payment Failed** - Sent on failed payment with product name, amount, and failure reason
* **Trial Ending Soon** - Sent before a [free trial](./products-and-pricing#free-trials) ends, so the customer knows the saved payment method will be charged

These apply to both one-time purchases and subscription renewals (receipts and failures). Customize them in **Emails -> Templates** (see the [Emails guide](/guides/apps/emails/guide)).
