Skip to main content

Klarna

Klarna renders its native payment widget inside your screen. It is not an express button: your own Pay button submits, /validate runs first, and Klarna's authorization runs inside the same call.

Setup​

ProductPurseCheckoutKlarna (brings the Klarna Mobile SDK through SPM)
InstallPluginRegistry.shared.install(KlarnaModule()), once, before you build a checkout
DashboardKlarna installments activated on the entity. The Klarna client token reaches the app through /init
ProjectA URL scheme in Info.plist, and the matching appReturnURL on every Klarna element
KeysNone in the app

The app return URL​

Klarna's widget can open a banking or identity app and must come back to yours. iOS has no applicationId for the SDK to synthesize a return URL from, the way Android does, so you supply it: both halves, and they must agree.

<!-- Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.example.store.klarna</string>
<key>CFBundleURLSchemes</key>
<array><string>com.example.store.klarna</string></array>
</dict>
</array>
let element = checkout.paymentElement(
for: item,
listener: listener,
redirect: presenter,
appReturnURL: URL(string: "com.example.store.klarna://return")
)

Without appReturnURL the element renders nothing and reports failed on submit with Klarna needs an app return URL: pass appReturnURL to paymentElement(for:…).

Render and pay​

Create the element when the shopper selects Klarna, then prepare() it: that runs POST /init for the client token and loads the widget.

struct KlarnaSection: View {
@ObservedObject var checkout: PurseHeadlessCheckout
let method: PrimaryMethod
@ObservedObject var element: PaymentElement

var body: some View {
VStack(spacing: 16) {
PaymentElementView(element) // the native widget, sized by Klarna
Button("Pay with Klarna") { element.submit() }
.disabled(!element.isReady)
}
.task {
try? checkout.setPrimary(method)
await element.prepare()
}
}
}

element.isReady turns true once the widget loaded. hasUI is true, selfSubmitting is false. submit() runs /validate, and only if it succeeded asks Klarna to authorize (finalizing when Klarna requires it). If /validate fails, Klarna is never asked.

Outcomes​

OutcomeWhen
completed(ValidationResult)/validate succeeded and Klarna authorized
failed(PurseError)The widget failed to load, /init or /validate failed, or Klarna declined or errored during its own authorization
A decline lands after the payment call

Klarna authorizes inside postValidation, so a decline arrives when /validate has already answered. The SDK reports it as failed with POST_VALIDATE_FAILED, wrapping a KlarnaAuthorizationDeclined you can match on, and the session is spent: do not offer a retry on the same session. This differs from Android, which maps a decline to Canceled.

Klarna's external bank or identity step is driven by Klarna's own widget through appReturnURL, not by the SDK's RedirectPresenter, so it does not surface as returnedFromRedirect. A failed before any submit (the widget did not load) is not a failed payment: nothing was charged, leave the shopper able to pick another method.

Instalment simulation​

A Klarna method flagged simulable can show the schedule before the shopper selects it, with checkout.simulateLoan(_:product:) and simulateLoans(_:product:): Advanced guides.

Troubleshooting​

SymptomCause
The widget shows a generic "see our terms" card, no instalment breakdownKlarna offers no instalment product for this amount and market. A Klarna-side configuration, not an SDK issue
failed with PARTNER_UI_ERROR, message klarna init returned no klarna_client_token in logs/init did not create a Klarna session for this item. Check the method's configuration
failed with API_ERROR, message klarna init not successfulThe /init was refused for this item
failed with PARTNER_UI_ERROR, message Klarna widget failed to loadThe widget could not initialize. The usual cause is a stale client token, because the session expired between selection and payment. Refresh the session
failed with POST_VALIDATE_FAILED right after tapping Pay/validate succeeded but Klarna declined the authorization. The session is spent
failed, message Klarna authorize callback missingsubmit() ran on an element whose prepare() never completed
After a bank app step, nothing comes backThe appReturnURL scheme is not declared in Info.plist, or the two do not match
The method is listed but has no UI and submitting reports METHOD_NOT_IMPLEMENTEDKlarnaModule was never installed. See Payment methods