> For the complete documentation index, see [llms.txt](https://docs.tiun.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tiun.io/guides/one-time-purchases/sell-one-time-products.md).

# Sell one-time products

This guide walks through wiring up a one-time purchase — a buy button, checkout, and gating content once the user has paid. Login and logout are not covered here; see [authenticating your user](/guides/authentication/authenticate-your-user.md) for those.

One-time products use the **same SDK call as subscriptions**. The difference is what happens afterwards: the customer pays a single fixed fee and keeps access permanently, so there's no renewal or expiration for your app to handle.

***

## 1. Create a one-time product

In the dashboard, create a product with the **One-time Purchase** pricing model and set its **Fixed fee** — the single amount charged at checkout. The full walkthrough is in [creating your first product](/guides/getting-started/creating-first-product.md); copy the product ID when you're done.

***

## 2. Install and initialize the SDK

Install `@tiun/sdk` and call `tiun.init` once at app startup. The [Quickstart](/quickstart.md) has the full install commands.

```javascript
import { tiun } from '@tiun/sdk';

tiun.init({
  snippetId: 'YOUR_SNIPPET_ID',
  language: 'en',
});
```

***

## 3. Add a buy button

Keep product IDs in one place so buy buttons, gating logic, and analytics all reference the same source. The JS will throw on typos instead of failing silently.

```javascript
const TIUN_PRODUCTS = {
  lifetime: 'p-live-lifetime',
};
```

Wire the button to `tiun.checkout()` with the matching product ID:

```javascript
function onClickBuy() {
  tiun.checkout({ productId: TIUN_PRODUCTS.lifetime });
}
```

When the user clicks, tiun opens the checkout overlay — it shows the fixed fee, collects their email, handles identity verification, processes the payment, and grants access in one flow. For what the overlay does behind the scenes, see [Checkout / How it works](/reference/checkout/how-it-works.md) in Reference.

***

## 4. Handle checkout success

After a successful purchase, `userChange` fires with `event: 'checkout'`. The user object includes their email and a `productAccess` array containing the product they just bought.

```javascript
tiun.on('userChange', (data) => {
  if (data.event === 'checkout' && data.user) {
    console.log('Purchased:', data.user.productAccess);
  }
});
```

That product ID **stays in `productAccess` permanently** — one-time purchases don't renew and don't expire, so your gate never has to handle revocation for them. See [One-time purchases](/reference/checkout/one-time-purchases.md) in Reference for the full lifecycle.

***

## 5. Gate content based on access

Use the `productAccess` array to decide what to show. The same `userChange` handler covers checkout success, login, logout, and the initial session restore on page load — so your gates stay in sync without extra work.

```javascript
tiun.on('userChange', (data) => {
  if (!data.isAuthenticated) {
    showSalesPage();
    return;
  }

  if (data.user.productAccess.includes(TIUN_PRODUCTS.lifetime)) {
    showPurchasedContent();
  } else {
    showSalesPage();
  }
});
```

Use the same check to **hide or disable the buy button** once the customer owns the product. They can't buy it a second time — checkout links their existing entitlement and shows an "already purchased" screen instead of charging — so a live buy button only sends a paying customer somewhere that can't do anything for them. See [One-time purchases](/reference/checkout/one-time-purchases.md) in Reference.

***

## 6. Add login and logout

Returning customers need to sign in to get their purchase back on a new browser or after logging out. The setup is covered in [authenticating your user](/guides/authentication/authenticate-your-user.md) — once that's wired up, the same `userChange` handler picks up their existing `productAccess`.

{% hint style="info" %}
Because a one-time purchase never expires, login is the only thing standing between a returning customer and the content they paid for. Make sure a **Log in** affordance is visible on your sales page, not just a buy button — otherwise a returning customer's only visible option is to pay again.
{% endhint %}

***

## 7. Go live

If you've been testing in sandbox, set up **live as a separate environment** in the dashboard, then for production traffic:

1. Remove `sandbox: true` from your `tiun.init` (or set it to `false`).
2. Use your **live** snippet ID and live product IDs (prefixed `p-live-`).
3. If you verify server-side, switch to the live API base URL and a live API key.

See [setting up your environment](/guides/getting-started/set-up-environment.md) for the full sandbox / live workflow.

***

## Full example

The one-time purchase integration in one place (login / logout wiring lives in the Auth guide):

```javascript
import { tiun } from '@tiun/sdk';

const TIUN_PRODUCTS = {
  lifetime: 'p-live-lifetime',
};

tiun.on('userChange', (data) => {
  if (!data.isAuthenticated) {
    showSalesPage();
    return;
  }

  if (data.user.productAccess.includes(TIUN_PRODUCTS.lifetime)) {
    showPurchasedContent();
  } else {
    showSalesPage();
  }
});

tiun.init({
  snippetId: 'YOUR_SNIPPET_ID',
  language: 'en',
});

function buyLifetime() {
  tiun.checkout({ productId: TIUN_PRODUCTS.lifetime });
}
```

***

For framework-specific implementations, see [React](/sdk/examples/react.md), [Vue](/sdk/examples/vue.md), and [Nuxt](/sdk/examples/nuxt.md) in the SDK reference.

If your backend needs to verify the purchase before serving protected data, use the same `productAccess`-based check documented in [verifying purchases server-side](/guides/subscriptions/verify-server-side.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.tiun.io/guides/one-time-purchases/sell-one-time-products.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
