Examples
Full create-a-charge-then-render-a-button integrations, one per stack. All four follow the same shape: a tiny backend creates a Charge with @klappay/node (your KLAP_API_KEY never reaches the browser), then hands the resulting chargeId to @klappay/one on the client.
Want to clone and run one instead of reading it here? See examples/ in the repo — four standalone, pnpm install && pnpm dev-ready apps. Each always depends on @klappay/one's latest npm release, so they double as a live integration check, not a frozen snapshot — see the repo's own examples/README.md for the "test against local, unpublished changes" workflow and the CI fallback behind it.
Vanilla (no bundler)
Mirrors klap-checkout's own zero-build setup — the <script> tag build straight from node_modules, no import resolution in the browser at all:
// src/server.ts — Hono
import { serveStatic } from '@hono/node-server/serve-static'
import { Hono } from 'hono'
import { createClient } from '@klappay/node'
const klap = createClient({
apiKey: process.env.KLAP_API_KEY!,
baseUrl: process.env.KLAP_BASE_URL!,
})
const app = new Hono()
// The IIFE build, served straight from node_modules — always matches
// whatever version is actually installed, no copying into ./public.
app.use(
'/vendor/one/*',
serveStatic({
root: './node_modules/@klappay/one/dist',
rewriteRequestPath: (path) => path.replace(/^\/vendor\/one/, ''),
}),
)
app.post('/api/charges', async (c) => {
const charge = await klap.charges.create({
amount: 25,
currency: 'USD',
expiresIn: 3600,
acceptedPayments: [{ token: 'USDC', network: 'base' }],
})
return c.json({ chargeId: charge.id })
})
export { app }<!-- public/index.html -->
<script src="/vendor/one/index.global.js"></script>
<button id="pay">Pay $25</button>
<script>
document.getElementById('pay').addEventListener('click', async () => {
const { chargeId } = await fetch('/api/charges', { method: 'POST' }).then((r) => r.json())
KlappayOne.createKlappayOne({
chargeId,
origin: 'https://klap.one',
onSuccess: (result) => alert(`Paid! ${result.txHash}`),
onError: (error) => alert(error.message),
}).open()
})
</script>See The button for the zero-JS <klappay-button> / data-klappay-one alternatives to the manual createKlappayOne() call above — either works equally well once the script tag is loaded this way.
Next.js (App Router)
A Route Handler for charge creation, a Client Component for the button — see React for why <KlappayButton /> needs a 'use client' boundary.
lib/klap.ts:
import { createClient } from '@klappay/node'
export const klap = createClient({
apiKey: process.env.KLAP_API_KEY!,
baseUrl: process.env.KLAP_BASE_URL!,
})app/api/charges/route.ts:
import { NextResponse } from 'next/server'
import { klap } from '@/lib/klap'
export async function POST() {
const charge = await klap.charges.create({
amount: 25,
currency: 'USD',
expiresIn: 3600,
acceptedPayments: [{ token: 'USDC', network: 'base' }],
})
return NextResponse.json({ chargeId: charge.id })
}app/checkout/CheckoutButton.tsx:
'use client'
import { useState } from 'react'
import { KlappayButton } from '@klappay/one/react'
export function CheckoutButton() {
const [chargeId, setChargeId] = useState<string | null>(null)
if (!chargeId) {
return (
<button
onClick={async () => {
const res = await fetch('/api/charges', { method: 'POST' })
const data = await res.json()
setChargeId(data.chargeId)
}}
>
Start checkout
</button>
)
}
return (
<KlappayButton
chargeId={chargeId}
origin={process.env.NEXT_PUBLIC_KLAP_ONE_ORIGIN!}
onSuccess={(result) => console.log('paid', result.txHash)}
/>
)
}Nuxt
Nitro server route for charge creation, a Vue component around the raw <klappay-button> element — see Other frameworks for the isCustomElement compiler option Nuxt needs.
server/api/charges.post.ts:
import { createClient } from '@klappay/node'
const klap = createClient({
apiKey: process.env.KLAP_API_KEY!,
baseUrl: process.env.KLAP_BASE_URL!,
})
export default defineEventHandler(async () => {
const charge = await klap.charges.create({
amount: 25,
currency: 'USD',
expiresIn: 3600,
acceptedPayments: [{ token: 'USDC', network: 'base' }],
})
return { chargeId: charge.id }
})app/components/CheckoutButton.vue:
<script setup lang="ts">
import { ref } from 'vue'
import { configure } from '@klappay/one'
const chargeId = ref<string | null>(null)
configure({ origin: useRuntimeConfig().public.klapOneOrigin })
async function start() {
const data = await $fetch('/api/charges', { method: 'POST' })
chargeId.value = data.chargeId
}
function onSuccess(event: Event) {
console.log('paid', (event as CustomEvent).detail.txHash)
}
</script>
<template>
<button v-if="!chargeId" @click="start">Start checkout</button>
<klappay-button v-else :charge-id="chargeId" @success="onSuccess" />
</template>SvelteKit
A +server.ts route for charge creation, the raw <klappay-button> element directly in the Svelte template:
src/routes/api/charges/+server.ts:
import { json } from '@sveltejs/kit'
import { createClient } from '@klappay/node'
import { KLAP_API_KEY, KLAP_BASE_URL } from '$env/static/private'
const klap = createClient({ apiKey: KLAP_API_KEY, baseUrl: KLAP_BASE_URL })
export async function POST() {
const charge = await klap.charges.create({
amount: 25,
currency: 'USD',
expiresIn: 3600,
acceptedPayments: [{ token: 'USDC', network: 'base' }],
})
return json({ chargeId: charge.id })
}src/routes/checkout/+page.svelte:
<script lang="ts">
import { configure } from '@klappay/one'
import { PUBLIC_KLAP_ONE_ORIGIN } from '$env/static/public'
let chargeId: string | null = null
configure({ origin: PUBLIC_KLAP_ONE_ORIGIN })
async function start() {
const res = await fetch('/api/charges', { method: 'POST' })
const data = await res.json()
chargeId = data.chargeId
}
function onSuccess(event: CustomEvent) {
console.log('paid', event.detail.txHash)
}
</script>
{#if !chargeId}
<button on:click={start}>Start checkout</button>
{:else}
<klappay-button charge-id={chargeId} on:success={onSuccess} />
{/if}Every example uses the public /v1 API
Same as @klappay/node/@klappay/checkout-kit's own examples: every example above calls klap.charges.create() against Core's public, API-key-authenticated /v1 surface — the same access any external merchant integration has. That means each example only ever creates charges under the KLAP_API_KEY you provide; see each example's own README for exactly which environment variables to set before running it.
