Switch event merch checkout after a deadline
Imagine you're selling a concert shirt. With JavaScript or React, the button opens the preorder checkout before the deadline and the regular merch checkout afterward. With HTML attributes, one offer can change its tier price at the deadline. Visitors see a countdown beside the button.
Set it up
- For JavaScript or React, publish two offers:
concert-shirt-preorderandconcert-shirt-standard. Give each its intended price. For HTML, publish oneconcert-shirtoffer with prices for the promo tiers. - Create a promo called
concert-merchwith a timed tier labeled Preorder, followed by a Standard tier. These labels match the code below. - Put the countdown and button on the merch page. Replace the host in the script URL or
apiBasewith your organization's real host.
For JavaScript or React, the promo tells the page which tier is active. The code maps that tier to a published offer. When the tier changes, the next click opens the new offer. It does not replace a checkout that someone already has open.
| JavaScript or React | They see | The button opens |
|---|---|---|
| Before the deadline | The preorder countdown | concert-shirt-preorder |
| After the deadline | The regular merch state | concert-shirt-standard |
For an HTML-only page, publish one offer named concert-shirt and configure its price for the active tier of concert-merch. The browser bundle renders the countdown and applies that tier's price when the buyer opens checkout.
<script defer
src="https://your-org.plandalf.dev/js/plandalf-sdk.js"></script>
<div data-plandalf-promo="concert-merch"></div>
<button
data-plandalf-present="concert-shirt"
data-plandalf-apply-promo="concert-merch"
>Buy the shirt</button>This changes the tier price within one offer. HTML attributes alone do not switch the button from concert-shirt-preorder to concert-shirt-standard. A declarative promo monitor follows any redirect URL configured on its active tier; the JavaScript and React examples explicitly disable that redirect. Use those programmatic examples for the two-offer behavior in the table above, and enforce any preorder cutoff in checkout configuration.
import { Plandalf } from '@plandalf/sdk'
const sdk = new Plandalf({
apiBase: 'https://your-org.plandalf.dev',
})
const button = document.querySelector('#buy-shirt')
let selectedOffer = null
const monitor = sdk.promo('concert-merch', {
target: '#countdown', redirect: false,
})
function selectTier(label) {
if (label === 'Preorder') {
selectedOffer = 'concert-shirt-preorder'
button.textContent = 'Preorder the shirt'
} else if (label === 'Standard') {
selectedOffer = 'concert-shirt-standard'
button.textContent = 'Buy the shirt'
} else {
selectedOffer = null
button.textContent = 'Merch unavailable'
}
button.disabled = !selectedOffer
}
void monitor.then(
(promo) => selectTier(promo?.activeTier?.label),
() => selectTier(null),
)
void (async () => {
for await (const event of monitor.events) {
if (event.type === 'tier-change') selectTier(event.to)
}
})()
button.addEventListener('click', async () => {
if (!selectedOffer) return
const result = await sdk.present(selectedOffer)
if (result.status === 'complete') {
// Ask your server to confirm the order before promising shipment.
}
})
// Call monitor.close() if this page is removed without navigation.<div id="countdown"></div>
<button id="buy-shirt" disabled>Checking offer…</button>import { useEffect, useRef, useState } from 'react'
import {
PlandalfProvider, usePlandalf, usePresent,
} from '@plandalf/react'
function MerchButton() {
const sdk = usePlandalf()
const { present, presenting } = usePresent()
const countdown = useRef<HTMLDivElement>(null)
const [tier, setTier] = useState<string | null>()
const offer = tier === 'Preorder' ? 'concert-shirt-preorder'
: tier === 'Standard' ? 'concert-shirt-standard' : null
useEffect(() => {
if (!countdown.current) return
let active = true
const monitor = sdk.promo('concert-merch', {
target: countdown.current, redirect: false,
})
void monitor.then(
(promo) => {
if (active) setTier(promo?.activeTier?.label ?? null)
},
() => { if (active) setTier(null) },
)
void (async () => {
for await (const event of monitor.events) {
if (active && event.type === 'tier-change') setTier(event.to)
}
})()
return () => { active = false; monitor.close() }
}, [sdk])
async function buy() {
if (!offer) return
const result = await present(offer)
if (result.status === 'complete') {
// Confirm the order on your server before promising shipment.
}
}
const label = tier === undefined ? 'Checking offer…'
: tier === 'Preorder' ? 'Preorder the shirt'
: offer ? 'Buy the shirt' : 'Merch unavailable'
return <>
<div ref={countdown} />
<button disabled={!offer || presenting} onClick={buy}>
{label}
</button>
</>
}
export function MerchPage() {
return (
<PlandalfProvider apiBase="https://your-org.plandalf.dev">
<MerchButton />
</PlandalfProvider>
)
}If you only need the price to change, use one offer with promo tier pricing and pass promo: 'concert-merch' to present(). That keeps checkout pricing tied to the active promo tier. The two-offer example above switches the button between genuinely different checkouts; it does not make the old preorder link inaccessible. If the old checkout must stop taking orders at the deadline, enforce that where the checkout is created. A browser countdown by itself cannot enforce a price or cutoff.
Inspect promo(), PromoHandle, and present() for the exact inputs and event types. See promo tier setup for the campaign configuration.