Embeddable checkout widget
Embed the Fynex checkout form as an inline component or popup overlay for test/demo-mode checkout sessions.
The Fynex checkout widget lets you embed the hosted checkout form directly on your own page for test/demo-mode checkout sessions. Instead of redirecting the customer to https://pay.fynex.ai/checkout/{session_id}, you load a small JavaScript SDK that renders the form inside an iframe — on your domain, in your layout.
Important
Widget iframe embedding is currently enabled only for test/demo-mode payments. Live cardholder checkout pages are protected with same-origin frame headers; use the full-page hosted checkout redirect for live payments.
The widget exposes two modes:
| Mode | Method | Use case |
|---|---|---|
| Embed | Fynex.embed() |
Inline form inside a container element on your page. |
| Popup | Fynex.popup() |
Modal overlay on top of your page. |
Note
For test/demo-mode sessions, the widget is an alternative to the full-page redirect described in Hosted Checkout. Both modes share the same checkout session lifecycle — you still create the session server-side and verify the outcome via polling or SSE. For live payments, use hosted checkout redirect.
How it works
Your backend verifies the final state with GET /payments-api/v1/payments/{payment_id}.
Step-by-step
- Create a test/demo checkout session from your backend
This is identical to the Hosted Checkout flow. POST /payments-api/v1/checkout with your Bearer token for a test/demo seller or terminal. See Hosted Checkout — Step 1 for the full request reference.
curl -sS -X POST https://api.fynex.ai/payments-api/v1/checkout \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"externalOrderRef": "ORDER-2099",
"amount": 79.00,
"currencyCode": "GBP",
"countryCode": "GB",
"returnUrls": {
"success": "https://example.com/orders/2099/success",
"failure": "https://example.com/orders/2099/failure"
}
}'
# Response: { "sessionId": "6f9b84e1-...", "checkoutUrl": "...", "expiresAt": "..." }
Pass the sessionId to your frontend — do not expose your Bearer token to the browser.
- Include the widget script
Add the script tag to your page. It must load before you call Fynex.embed() or Fynex.popup(). The script is served with Cache-Control: public, max-age=3600 and Access-Control-Allow-Origin: *, so it can be loaded from any domain.
<script src="https://api.fynex.ai/widget/checkout.js"></script>
No API key or auth header is needed for the script itself — it is a public static asset.
- Launch the widget
Choose embed (inline) or popup (modal) depending on your UX preference.
Embed mode — renders the checkout form inside a container element you control:
<div id="payment-form"></div>
<script>
var checkout = Fynex.embed({
sessionId: SESSION_ID, // string — from your backend
container: '#payment-form', // CSS selector or DOM element
onSuccess: function(data) {
console.log('Payment succeeded', data);
// Do NOT fulfil the order here — verify server-side first
},
onFailure: function(data) {
console.log('Payment failed', data);
}
});
// To remove the widget later (e.g. after navigating away):
// checkout.destroy();
</script>
Popup mode — opens a modal overlay over your page:
<button id="pay-btn">Pay now</button>
<script>
document.getElementById('pay-btn').addEventListener('click', function() {
var popup = Fynex.popup({
sessionId: SESSION_ID,
onSuccess: function(data) {
console.log('Payment succeeded', data);
},
onFailure: function(data) {
console.log('Payment failed', data);
},
onClose: function() {
console.log('Customer closed the popup');
}
});
// To close programmatically:
// popup.close();
});
</script>
- Verify the payment server-side
The onSuccess callback is a client-side signal only — it fires when the iframe posts a success message to your page. A determined user could fire a postMessage manually. Always verify the final payment state on your backend before fulfilling the order.
// On your server, after onSuccess fires and the browser POSTs your endpoint:
const res = await fetch(
`${process.env.FYNEX_API}/payments-api/v1/payments/${encodeURIComponent(externalOrderRef)}`,
{ headers: { Authorization: `Bearer ${process.env.FYNEX_TOKEN}` } }
);
const payment = await res.json();
if (payment.lifecycleStatus === 'captured') {
fulfillOrder();
}
See Polling & SSE for a complete server-side verification reference.
API reference
Fynex.embed(opts) → { destroy() }
| Option | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | The checkout session ID returned by POST /payments-api/v1/checkout. |
container |
string | Element | Yes | CSS selector (e.g. '#payment-form') or a DOM element reference. |
onSuccess |
function(data) | No | Called when the payment completes successfully. |
onFailure |
function(data) | No | Called when the payment fails. |
Returns an object with a destroy() method that removes the iframe and cleans up event listeners.
Fynex.popup(opts) → { close() }
| Option | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | The checkout session ID. |
onSuccess |
function(data) | No | Called when the payment completes successfully. |
onFailure |
function(data) | No | Called when the payment fails. |
onClose |
function() | No | Called when the overlay is dismissed (close button or click-outside). |
Returns an object with a close() method that dismisses the overlay programmatically.
Events (postMessage)
The widget communicates via window.postMessage. The widget SDK handles these internally; you interact through the callbacks above. Messages carry { type: 'fynex-checkout', event: 'success' | 'failure' | 'close' | 'resize', data: {} }.
Common pitfalls
Warning
Do not fulfil orders from
onSuccessalone. The callback fires on a client-sidepostMessage. Always verify the payment state server-side —GET /payments-api/v1/payments/{payment_id}(yourexternalOrderRef) with your bearer token, or the GraphQLgenericPaymentquery from a dashboard session — before dispatching goods or services.
Caution
Session expiry. Checkout sessions have a server-side TTL (returned as
expiresAt). If the customer takes too long, the iframe will show an expiry error. Create a fresh session and re-initialise the widget.
containernot found.Fynex.embedthrows"Fynex.embed: container not found"if the selector matches no element. Ensure the DOM node exists before callingembed().sessionIdis required. Bothembedandpopupthrow immediately ifsessionIdis missing or falsy.- Widget behind a strict CSP. The iframe loads
https://api.fynex.ai. Ensure your Content-Security-Policyframe-srcdirective allowshttps://api.fynex.ai. - Live checkout sessions. Live payment pages are not iframe-embeddable by default. If you pass a live
sessionIdto the widget, browser frame protections can block the iframe. Use the hosted checkout redirect for live payments. - 3DS inside the iframe. For test/demo widget sessions, 3DS challenges are handled entirely within the embedded iframe — your page does not need to manage any redirect. The
onSuccess/onFailurecallback fires once the full payment flow (including any 3DS step) is complete.
See also
- Hosted Checkout — Full-page redirect alternative — lowest integration effort.
- Polling & SSE — Server-side payment verification after onSuccess fires.
- Server-to-server payments — Full control over the payment flow without any hosted page.