"Creates a Klarna payment button that handles the customer flow.
You provide an initiate callback that returns either a paymentRequestUrl
(created server-side) or PaymentRequestData (created client-side),
and Klarna takes care of launching the purchase flow.
Register event handlers (complete, error, abort) before mounting
the button so that all events are captured from the first interaction."
// 1. Register event handlers first
klarna.Payment.on("complete", async (paymentRequest) => {
console.log("Payment completed:", paymentRequest.state);
});
klarna.Payment.on("error", async (error) => {
console.error("Payment error:", error);
});
// 2. Create and mount the button
klarna.Payment.button({
initiate: async ({ klarnaNetworkSessionToken }) => {
// Create the payment request on your server
const response = await fetch("/api/create-payment-request", {
method: "POST",
body: JSON.stringify({ klarnaNetworkSessionToken }),
});
const { paymentRequestUrl } = await response.json();
return { paymentRequestUrl };
},
}).mount("#klarna-button-container");
"Cancels the payment request with the given ID. It's only possible to cancel payment requests created by the same client/credentials.
Pass the payment request URL received from the payment request creation response.
Use this when the customer clears their cart, starts a new checkout session, or you otherwise need to invalidate an in-progress payment request."
// Cancel an existing payment request when the customer empties their cart
const cancelledRequest = await klarna.Payment.cancel("kr_payment_req_123");
console.log("Payment request cancelled, state:", cancelledRequest.state);
"Fetches the current state of a payment request. It's only possible to fetch payment requests created by the same client/credentials.
Pass the payment request URL received from the payment request creation response.
This is useful in redirect flows where the customer returns to your site and you need to check the payment request state to determine the outcome."
// Fetch by payment request Url
const paymentRequest = await klarna.Payment.fetch("https://pay.klarna.com/l/...");
if (paymentRequest.state === "COMPLETED") {
const token = paymentRequest.stateContext?.klarnaNetworkSessionToken;
// Send the token to your backend for authorization
await authorizePayment(token);
} else if (paymentRequest.state === "DECLINED") {
console.error("Payment declined:", paymentRequest.stateReason);
}
Optionaloptions: PaymentRequestOptions"Initiates a payment with Klarna. Use this method only if you absolutely need to own your
button. Otherwise, use the {@docs-portal-link Payment.button} method. If you use this method
along with initiationMode of DEVICE_BEST or ON_PAGE, it has to be called right after
the button is clicked. If there are long running operations such as network requests, you
can do that within the callback."
// Prefer Payment.button() — it handles click timing and popup blockers automatically.
// Use initiate() only if you need full control over your own button.
// Call initiate immediately after a click event.
// Move network requests inside the callback to avoid browser popup blockers.
let isInitiating = false;
// Suppose "payWithKlarnaButton" is your checkout button
payWithKlarnaButton.addEventListener('click', async () => {
if (isInitiating) {
return;
}
try {
isInitiating = true;
payWithKlarnaButton.disabled = true;
await klarna.Payment.initiate(async ({ klarnaNetworkSessionToken }) => {
// Do network requests inside the callback.
// The klarnaNetworkSessionToken can optionally be sent to your backend
// for server-side payment request creation.
const paymentData = await createPaymentSession(klarnaNetworkSessionToken);
return {
currency: "USD",
amount: paymentData.amount,
customerInteractionConfig: {
returnUrl: "https://example.com/success",
},
};
}, {
initiationMode: "DEVICE_BEST",
});
} catch (error) {
console.error('Payment initiation failed:', error);
} finally {
isInitiating = false;
payWithKlarnaButton.disabled = false;
}
});
"Unregister an event handler for the given event. Pass the same function
reference that was used when registering with on(). This is useful for
cleanup when your component unmounts or when you need to swap handlers."
// Keep a reference to the handler so it can be removed later
const onComplete = async (paymentRequest) => {
await handleCompletion(paymentRequest);
return true;
};
// Register
klarna.Payment.on("complete", onComplete);
// Later, unregister (e.g. on component unmount)
klarna.Payment.off("complete", onComplete);
"Register an event handler for the complete event. A payment request is
completed when the customer has approved the purchase.
During a redirection flow the complete handler will trigger once your
page has loaded on the success page. Pending updates are triggered even if
the event handler is registered after the page has loaded to avoid the
need for polling.
Return false from the callback to prevent automatic redirection to the
returnUrl (e.g. if you want to show a custom confirmation screen). Return true and
Klarna navigates to the returnUrl, emitting the redirect event first if you
registered a handler for it. The complete handler always runs before that redirect."
klarna.Payment.on("complete", async (paymentRequest) => {
const token = paymentRequest.stateContext?.klarnaNetworkSessionToken;
// Send the token to your backend for authorization
await fetch("https://example.com/api/authorize", {
method: "POST",
body: JSON.stringify({ klarnaNetworkSessionToken: token }),
});
// Return false to prevent automatic redirection to returnUrl
return false;
});
"Register an event handler for the error event. The handler is called
when initiate() (including a click on the payment button), fetch() or
cancel() fails. The same error is still thrown, or the returned promise
rejected, after the handler has been called, so try/catch around your
own calls keeps working. A failed click on the payment button has no caller
to catch the error, so this handler is the only place to handle it.
The handler is not called when the customer closes the payment window
before finishing, which is a cancellation rather than an error, or for
presentation(), which falls back to a default presentation when the
backend call fails and rejects when required input such as currency
is missing.
The error parameter may be a PaymentError (with structured
errorCode and errorMessage fields) or a generic Error."
klarna.Payment.on("error", async (error, paymentRequest) => {
if ("errorCode" in error) {
// Structured PaymentError
console.error(`Payment error [${error.errorCode}]: ${error.errorMessage}`);
} else {
// Generic Error
console.error("Unexpected error:", error.message);
}
if (paymentRequest) {
console.log("Payment request state:", paymentRequest.state);
}
});
"Register an event handler for the abort event. This fires when the
customer cancels the payment request by closing the popup or otherwise
stopping the process. This is distinct from {@docs-portal-link Payment.cancel}, which is
initiated programmatically by the integrator."
"Register an event handler for the redirect event. Klarna emits it before it
navigates the browsing context that loaded the SDK: to launch the purchase journey as a
redirect (phones, tablets and in-app browsers), when your initiate callback resolves with
a returnUrl, and after a journey that ran in a pop-up or inside the page completes and
Klarna navigates to the return URL. The handler receives the redirectUrl the customer has
to be sent to.
Resolve true to let Klarna navigate the browsing context that loaded the SDK. Resolve
false to take the navigation over yourself, for example when the SDK runs inside an
iframe and the top-level page has to navigate. If no handler is registered, Klarna
navigates the browsing context that loaded the SDK.
After a completed purchase, the complete handler always runs first, so you can read the
payment request, including its token, before any navigation. If the complete handler
resolves false, Klarna doesn't navigate to the return URL and doesn't emit redirect.
If it resolves true, Klarna emits redirect with the return URL before navigating.
Register the handler before mounting the {@docs-portal-link Payment.button} or calling {@docs-portal-link Payment.initiate}."
// Inside a framed checkout page: hand the navigation to the parent page.
// At the top level, register no handler, so Klarna navigates the page itself.
if (window.parent !== window) {
klarna.Payment.on("redirect", async ({ redirectUrl }) => {
window.parent.postMessage(
{ type: "klarna:flow-redirect", redirectUrl },
"https://partner.example.com"
);
// Return false so Klarna doesn't navigate the iframe
return false;
});
}
The event name.
Event callback handler.
"Register a handler for the overlayopen event. Klarna emits it on the Payment
namespace right before flow interaction mode draws anything over the page for a
payment-initiated flow: the backdrop behind the Klarna pop-up, the backdrop shown when the
browser blocks the pop-up, or the in-page fallback iframe. This includes the payment button and
links rendered through {@docs-portal-link Payment.presentation} such as Check your purchase
power.
Register the handler before mounting {@docs-portal-link Payment.button} or calling {@docs-portal-link Payment.presentation}. If you also mount {@docs-portal-link Messaging.placement} in the same framed page, register the same handler on {@docs-portal-link Messaging.onOverlayOpen} for placement-driven overlays, and count the overlays that are open: a Messaging overlay can open while a Payment overlay is still fading out."
// Inside a framed checkout page. One count covers the overlays of both namespaces.
let openOverlays = 0;
const onOverlayOpen = () => {
openOverlays += 1;
if (openOverlays > 1) return;
pinKlarnaContainer();
window.parent.postMessage({ type: "klarna:flow-open" }, "https://partner.example.com");
};
if (window.parent !== window) {
klarna.Payment.on("overlayopen", onOverlayOpen);
// If the page also mounts Messaging.placement (MESSAGING in products):
// klarna.Messaging.on("overlayopen", onOverlayOpen);
}
The event name.
Event callback handler.
"Register a handler for the overlayclose event. Klarna emits it on the Payment
namespace after flow interaction mode removes the backdrop or in-page fallback iframe for a
payment-initiated flow. The event may fire before a redirect navigation completes while the
document is still alive, and may not fire if the browsing context is torn down first.
It has no fixed order relative to complete and abort. With initiationMode ON_PAGE and
the journey in the Klarna pop-up, the backdrop stays after the pop-up closes until Klarna has
confirmed the outcome, so overlayclose fires after them. With POPUP, or when the journey
runs in the in-page fallback iframe, Klarna starts removing the overlay when the journey ends
and confirms the outcome at the same time, so either can come first. Restore a resized iframe
on overlayclose, not on complete.
Register the handler before mounting {@docs-portal-link Payment.button} or calling {@docs-portal-link Payment.presentation}. If you also mount {@docs-portal-link Messaging.placement} in the same framed page, register the same handler on {@docs-portal-link Messaging.onOverlayClose} for placement-driven overlays, and restore a resized iframe only once every open overlay has closed."
// Inside a framed checkout page. openOverlays is the count from the overlayopen example
// (let openOverlays = 0), which adds one for each overlay that opens.
const onOverlayClose = () => {
if (openOverlays === 0) return;
openOverlays -= 1;
if (openOverlays > 0) return;
unpinKlarnaContainer();
window.parent.postMessage({ type: "klarna:flow-close" }, "https://partner.example.com");
};
if (window.parent !== window) {
klarna.Payment.on("overlayclose", onOverlayClose);
// If the page also mounts Messaging.placement (MESSAGING in products):
// klarna.Messaging.on("overlayclose", onOverlayClose);
}
"The shipping address changed event handler is triggered when the customer changes their shipping address during the payment flow. If the shipping address is present in the customer account, the event will be triggered immediately after the customer has logged in. Here, you are given the opportunity to accept or reject the shipping address.
If you did not return anything during the 10 seconds after receiving this event, the customer will see the error screen and payment request will be aborted."
klarna.Payment.on("shippingaddresschange", (paymentRequest, shippingAddress) => {
// 1. Accept — return a list of shipping options.
// The customer will be prompted to select one.
if (canShipTo(shippingAddress)) {
return {
shippingOptions: [
{
shippingOptionReference: "standard",
amount: 500,
displayName: "Standard Shipping",
description: "3-5 business days",
},
{
shippingOptionReference: "express",
amount: 1500,
displayName: "Express Shipping",
description: "1-2 business days",
shippingType: "EXPRESS",
},
],
// 2. Accept with preselection — return selectedShippingOptionReference along
// with updated amount and lineItems to skip the selection screen.
amount: totalWithShipping,
lineItems: [
...lineItems,
{ name: "Standard Shipping", quantity: 1, totalAmount: 500 },
],
selectedShippingOptionReference: "standard",
};
}
// 3. Reject — return a rejectionReason to prompt the customer for a new address.
// Available reasons: POSTAL_CODE_NOT_SUPPORTED, CITY_NOT_SUPPORTED,
// REGION_NOT_SUPPORTED, COUNTRY_NOT_SUPPORTED, ADDRESS_NOT_SUPPORTED.
return {
rejectionReason: "COUNTRY_NOT_SUPPORTED",
};
});
Shipping option selected event handler callback
"The shipping option selected event handler is triggered when the customer selects a shipping option during the payment flow. Here, you are given the opportunity to accept or reject the shipping option.
If you did not return anything during the 10 seconds after receiving this event, the customer will see the error screen and payment request will be aborted."
klarna.Payment.on("shippingoptionselect", async (paymentRequest, shippingOption) => {
// 1. Accept — return the updated order payload with the selected shipping
// cost added to amount and lineItems.
if (isValidOption(shippingOption)) {
const { amount } = await fetch("/cart", {
method: "POST",
body: JSON.stringify({ shipping_option: shippingOption }),
});
return {
amount,
lineItems: [
...lineItems,
{ name: "Shipping", quantity: 1, totalAmount: 500 },
],
};
}
// 2. Reject — return a rejectionReason to prompt the customer to select
// a different option. Available reasons: INVALID_OPTION.
return {
rejectionReason: "INVALID_OPTION",
};
});
"Returns instructions on how Klarna should be presented in the payment selector
and UI components to render.
Use the instruction field to determine whether Klarna should be shown, preselected,
shown exclusively, or hidden. Then mount the returned UI components (icon, header,
subheader, message, and paymentButton) into your checkout page.
The paymentButton is the component the customer clicks to start the Klarna purchase flow."
// Fetch the presentation, handle the instruction, and mount Klarna UI components.
const paymentPresentation = await klarna.Payment.presentation({
amount: 11800,
currency: "USD",
locale: "en-US",
intent: "PAY",
});
if (paymentPresentation) {
switch (paymentPresentation.instruction) {
case "SHOW_KLARNA":
renderKlarnaOption(paymentPresentation);
break;
case "PRESELECT_KLARNA":
renderKlarnaOption(paymentPresentation);
break;
case "SHOW_ONLY_KLARNA":
document.getElementById("other-payment-methods")?.remove();
renderKlarnaOption(paymentPresentation);
break;
case "HIDE_KLARNA":
document.getElementById("klarna-option")?.remove();
break;
default:
renderKlarnaOption(paymentPresentation);
break;
}
}
function renderKlarnaOption(paymentPresentation) {
const klarnaContainer = document.getElementById("klarna-option");
if (!klarnaContainer) {
return;
}
const { paymentOption } = paymentPresentation;
if (!paymentOption) {
return;
}
klarnaContainer.innerHTML = `
<div id="klarna-icon-container"></div>
<div id="klarna-header-container"></div>
<div id="klarna-subheader-container"></div>
<div id="klarna-message-container"></div>
<div id="klarna-button-container"></div>
`;
// Mount Klarna UI components
paymentOption.icon
.component({ shape: "badge" })
.mount("#klarna-icon-container");
paymentOption.header
.component()
.mount("#klarna-header-container");
paymentOption.subheader
.component()
.mount("#klarna-subheader-container");
if (paymentOption.message) {
paymentOption.message
.component()
.mount("#klarna-message-container");
}
// Mount the payment button — the customer clicks this to start the Klarna flow
paymentOption.paymentButton
.component({
initiate: async ({ klarnaNetworkSessionToken, paymentOptionId }) => {
const response = await fetch("/api/create-payment-request", {
method: "POST",
body: JSON.stringify({ klarnaNetworkSessionToken, paymentOptionId }),
});
const { paymentRequestUrl } = await response.json();
return { paymentRequestUrl };
},
})
.mount("#klarna-button-container");
}
The Klarna Payment interface provides access to the payment request API.
Accepting a payment using the Klarna Payment button:
Example Group
label="Example"
Example: Typescript
Example: HTML
Package Interface