mq-checkout
A use-case agnostic payment gateway web component. Embeds the Mediquo payment portal, polls for completion and reports the outcome through generic events.
<mq-checkout> renders only the payment gateway: it embeds the Mediquo payment portal in an iframe, polls the backend until the payment resolves, and emits a generic outcome. It knows nothing about appointments, services or any specific product — the host decides how a payment session is created by supplying a provideSession callback.
If you are charging for an appointment, use <mq-appointment-checkout> instead — it wraps <mq-checkout> and creates the session for you from a slot and service.
How it works
- You provide a
provideSession()async callback that returns{ sessionId, paymentUrl }. <mq-checkout>authenticates (api-key+token), then callsprovideSession()and embedspaymentUrl.- It polls the payment status every 3 seconds (up to 10 minutes).
- On
paidit firespayment-success; ondeniedit firespayment-denied; on timeout/auth failure it firespayment-error. - If the portal asks to retry,
<mq-checkout>callsprovideSession()again to get a fresh session — the integrator does nothing.
Dependencies
<mq-checkout> must be rendered inside an <mq-theme-provider> ancestor, which supplies the design tokens it styles against.
It does not require <mq-query-client-provider> or <mq-socket-provider>.
Installation
npm install @mediquo/web-componentsimport "@mediquo/web-components/mq-checkout";
import "@mediquo/web-components/mq-theme-provider";Usage
provideSession is a function and therefore must be assigned as a JS
property — it cannot be an HTML attribute.
HTML / vanilla JS
<mq-theme-provider theme="mediquo">
<mq-checkout
id="checkout"
env="production"
api-key="<your-api-key>"
token="<your-token>"
locale="es_ES"
></mq-checkout>
</mq-theme-provider>
<script type="module">
const checkout = document.getElementById("checkout");
// Called for the initial load AND on every retry.
checkout.provideSession = async () => {
const res = await fetch("https://your-backend.example/payments", {
method: "POST",
});
const { sessionId, paymentUrl } = await res.json();
return { sessionId, paymentUrl };
};
checkout.addEventListener("payment-success", (e) => {
console.log("paid", e.detail.sessionId, e.detail.reference);
});
</script>React / Next.js
Set the callback through a ref (web component properties cannot be passed as JSX attributes when they are functions):
"use client";
import { useEffect, useRef } from "react";
export function Checkout({ apiKey, token, createSession }) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
import("@mediquo/web-components/mq-checkout");
import("@mediquo/web-components/mq-theme-provider");
}, []);
useEffect(() => {
const el = ref.current as (HTMLElement & { provideSession?: () => Promise<unknown> }) | null;
if (!el) return;
el.provideSession = createSession;
const onSuccess = (e: Event) => console.log((e as CustomEvent).detail);
el.addEventListener("payment-success", onSuccess);
return () => {
el.removeEventListener("payment-success", onSuccess);
};
}, [createSession]);
return (
<div style={{ height: "700px" }}>
{/* @ts-expect-error — custom element */}
<mq-theme-provider theme="mediquo">
{/* @ts-expect-error — custom element */}
<mq-checkout
ref={ref}
env="production"
api-key={apiKey}
token={token}
locale="es_ES"
/>
</mq-theme-provider>
</div>
);
}Attributes & properties
| Name | Kind | Type | Required | Description |
|---|---|---|---|---|
env | attribute | string | No | Backend environment: "development" or "production". Applied once at mount. |
api-key | attribute | string | Yes | API key for the Mediquo platform. Used to authenticate the status polling. |
token | attribute | string | Yes | Mediquo user token, exchanged internally for an access token. |
locale | attribute | string | No | Locale code: es_ES, en_US, pt_PT, de_DE, ca_ES. Defaults to es_ES. |
provideSession | property | () => Promise<{ sessionId: string; paymentUrl: string }> | Yes | Async factory returning the payment session. Invoked for the initial load and on every retry. Must be set as a JS property. |
Events
All events bubble and cross shadow boundaries (composed: true).
| Event | Detail | Description |
|---|---|---|
payment-success | { sessionId: string; reference: string } | Payment confirmed. reference is the backend reference for the paid resource. |
payment-denied | { sessionId: string } | Payment was rejected by the gateway. Polling stops immediately. |
payment-error | { reason: "auth" | "session" | "timeout" } | Authentication failed, the session could not be created, or polling timed out (10 minutes). |
payment-retry | — | Notification only. The portal requested a retry; <mq-checkout> re-creates the session automatically. No action required. |
Listening to events
const checkout = document.querySelector("mq-checkout");
checkout.addEventListener("payment-success", (e) => {
console.log("paid", e.detail.reference);
});
checkout.addEventListener("payment-denied", () => {
console.log("denied");
});
checkout.addEventListener("payment-error", (e) => {
console.log("error", e.detail.reason);
});Environment
Select the backend with the env attribute ("development" or "production", default "production").
Set
envonce at mount. Changing it after the component has authenticated rebuilds the underlying API client and drops the in-flight session.
Content Security Policy
The component embeds the payment portal in an <iframe>. If your app sets a CSP, allowlist the portal origin under frame-src (the origin matches the paymentUrl returned by provideSession, and differs per environment).
Theming
Wrap the component in <mq-theme-provider> to apply a preset or custom tokens:
<mq-theme-provider theme="custom" style="--color-primary: #e63946;">
<mq-checkout ...></mq-checkout>
</mq-theme-provider>Available preset themes: mediquo, adeslas, default.