React
CheckoutWidget.tsx
import { useEffect, useRef } from 'react';
declare global {
interface Window {
HelaMesh?: { mount: (target: any, opts: any) => { destroy: () => void } };
}
}
export function CheckoutWidget({ invoiceId }: { invoiceId: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let instance: { destroy: () => void } | null = null;
const tryMount = () => {
if (!ref.current || !window.HelaMesh) return false;
instance = window.HelaMesh.mount(ref.current, {
invoiceId,
publishableKey: process.env.NEXT_PUBLIC_HELAMESH_PK!,
onPaid: (event) => {
// Update your local state, redirect, fulfil order
window.location.href = `/order/success?inv=${event.invoice.id}`;
},
});
return true;
};
if (!tryMount()) {
// SDK not loaded yet โ wait for it
const script = document.createElement('script');
script.src = 'https://pay.helamesh.com/sdk/v1.js';
script.async = true;
script.onload = tryMount;
document.body.appendChild(script);
}
return () => instance?.destroy();
}, [invoiceId]);
return <div ref={ref} />;
}
The component is idempotent โ tryMount checks if the SDK is already loaded (e.g., if another component loaded it first). The destroy() cleanup runs on unmount so nothing leaks.