# Sell your first offer

Build one checkout for a course and put a working **Join the course** button on your own site. The same steps work for a download, workshop, or physical product; replace the example name and slug with yours.

## 1. Prepare the offer

1. In Plandalf, create or import the product and its price. Connect the payment integration this offer will use.
2. Create an offer for that product. Choose a checkout template, set the customer fields and confirmation behavior, and give the offer a slug. This guide uses `ceramics-course`.
3. Preview the offer. Check the product, currency, total, discounts, and what the buyer sees after checkout. Publish it when that flow is right. [Offer setup](https://plandalf.com/docs/product-guides/checkouts/create) covers each editor step.

Keep the **offer slug**, not the product ID or the offer title. Also note your organization's host. The examples below use `https://your-org.plandalf.dev`; replace it with your real host.

## 2. Put the button on your page

Choose the integration that matches the page you already have. All three open the same published offer.

<scalar-tabs default="HTML">
<scalar-tab title="HTML">

Add the script once and put the attribute on the button. The browser SDK binds the click for you.

```html title="course.html"
<script defer
  src="https://your-org.plandalf.dev/js/plandalf-sdk.js"></script>
<button data-plandalf-present="ceramics-course">
  Join the course
</button>
```

</scalar-tab>
<scalar-tab title="JavaScript">

Install `@plandalf/sdk` in your JavaScript app. The returned handle is awaitable; a dismissal is a result, while an opening failure rejects.

```javascript title="course.js"
import { Plandalf } from '@plandalf/sdk'

const sdk = new Plandalf({
  apiBase: 'https://your-org.plandalf.dev',
})
const button = document.querySelector('#join-course')
const message = document.querySelector('#course-message')

button.addEventListener('click', async () => {
  button.disabled = true
  try {
    const result = await sdk.present('ceramics-course')
    message.textContent = result.status === 'complete'
      ? 'Checkout finished. We are confirming your place.'
      : 'You can join later.'
  } catch {
    message.textContent =
      'Checkout could not open. Please try again.'
  } finally {
    button.disabled = false
  }
})
```

```html title="course.html"
<button id="join-course">Join the course</button>
<p id="course-message" role="status"></p>
```

</scalar-tab>
<scalar-tab title="React">

Install `@plandalf/react` and place the provider above the button.

```tsx title="CourseButton.tsx"
import { useState } from 'react'
import { PlandalfProvider, usePresent } from '@plandalf/react'

function CourseButton() {
  const { present, presenting } = usePresent()
  const [message, setMessage] = useState('')

  async function join() {
    try {
      const result = await present('ceramics-course')
      setMessage(result.status === 'complete'
        ? 'Checkout finished. We are confirming your place.'
        : 'You can join later.')
    } catch {
      setMessage('Checkout could not open. Please try again.')
    }
  }

  return <>
    <button disabled={presenting} onClick={join}>
      Join the course
    </button>
    <p role="status">{message}</p>
  </>
}

export function CoursePage() {
  return <PlandalfProvider apiBase="https://your-org.plandalf.dev">
    <CourseButton />
  </PlandalfProvider>
}
```

</scalar-tab>
</scalar-tabs>

## 3. Test the whole journey

1. Load your page and click the button. Confirm the published `ceramics-course` offer opens with the expected price and payment environment. If you plan to make a test payment, use an offer and integration that your organization has explicitly configured for sandbox use. A `mode: 'test'` override can fall back to live checkout for public visitors; it is not a test-mode safety switch.
2. Dismiss checkout. The page should remain usable; JavaScript and React receive `status: 'dismissed'`.
3. On a confirmed sandbox setup, complete a provider-supported test payment. JavaScript and React receive `status: 'complete'`, but that browser result does **not** prove the payment settled.
4. Check the payment in the intended provider/account and have your server confirm the order before granting course access, sending a download, or shipping a product.

If checkout cannot find the offer, check the host, published slug, and payment setup. For a fuller course integration, see the [SDK example](https://plandalf.com/docs/guides/await-a-checkout). The exact call and result fields are in [`present()`](https://plandalf.com/docs/sdk/present), [`FlowOpenOptions`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions), and [`FlowResult`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult).

Source: https://plandalf.com/docs/start/first-checkout
