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
- In Plandalf, create or import the product and its price. Connect the payment integration this offer will use.
- 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. - Use Preview to check the product, currency, total, discounts, and what the buyer sees after checkout. Open Share for the hosted link or your site's install instructions. Offer setup covers the editor and publishing steps.
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.
Add the script once and put the attribute on the button. The browser SDK binds the click for you.
<script defer
src="https://your-org.plandalf.dev/js/plandalf-sdk.js"></script>
<button data-plandalf-present="ceramics-course">
Join the course
</button>Install @plandalf/sdk in your JavaScript app. The returned handle is awaitable; a dismissal is a result, while an opening failure rejects.
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
}
})<button id="join-course">Join the course</button>
<p id="course-message" role="status"></p>Install @plandalf/react and place the provider above the button.
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>
}3. Test the whole journey
- Connect Stripe (Test) in Settings → Advanced. For this embedded button, add your development hostname under Test domains and save it. For a direct hosted checkout, open Share → Test mode link while signed in as an organization member. Hosted checkout explains both paths. A
mode: 'test'override alone does not guarantee test payments. - Load your page and click the button. Confirm the published
ceramics-courseoffer opens with the expected price and test payment environment. Without a connected Stripe test account or an authorized test context, checkout can remain live. - Dismiss checkout. The page should remain usable; JavaScript and React receive
status: 'dismissed'. - On the confirmed test setup, complete a Stripe test payment. JavaScript and React receive
status: 'complete', but that browser result does not prove the payment settled. - Check the test invoice and the payment in the Stripe test account. Test purchases skip automation sequences unless Run on test purchases is enabled. Have your server confirm the order before granting course access, sending a download, or shipping a product.
After testing, publish the checkout and your site. Production pages should use the live payment integration and stay outside the Test domains list.
If checkout cannot find the offer, check the host, published slug, and payment setup. For a fuller course integration, see the SDK example. The exact call and result fields are in present(), FlowOpenOptions, and FlowResult.