> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gr4vy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe - Klarna

> Configure Klarna via Stripe as a payment method in Gr4vy.

export const connector = {
  displayName: "Klarna",
  method: "klarna",
  features: "create_token create_transaction deep_linking delayed_capture delete_token direct_capture direct_integration_create partial_capture partial_refunds payment_method_tokenization redirect_requires_popup refunds requires_webhook_setup transaction_sync verify_credentials void",
  supportedCountries: "AT AU BE CA CH CZ DE DK ES FI FR GB GR IE IT NL NO NZ PL PT RO SE US",
  supportedCurrencies: "AUD CAD CHF CZK DKK EUR GBP NOK NZD PLN RON SEK USD"
};

export const ConnectorRegions = ({data, kind, name: nameOverride}) => {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const isCountries = kind === "countries";
  const raw = data && (isCountries ? data.supportedCountries : data.supportedCurrencies);
  const codes = typeof raw === "string" ? raw.split(/\s+/).filter(Boolean) : Array.isArray(raw) ? raw : [];
  const DISPLAY_NAME_OVERRIDES = {
    authorizenet: "Authorize.net",
    cardpointe: "Fiserv CardPointe",
    dlocal: "dLocal",
    shift4i4go: "Shift4 i4go",
    tokenex: "TokenEx"
  };
  const rawName = data && data.displayName || "";
  const name = nameOverride || DISPLAY_NAME_OVERRIDES[rawName.toLowerCase()] || rawName || "This connector";
  const verb = isCountries ? "supports transactions from buyers in" : "supports processing payments in";
  const noun = isCountries ? "countries" : "currencies";
  if (codes.length === 0) return null;
  let displayNames = null;
  try {
    displayNames = new Intl.DisplayNames(["en"], {
      type: isCountries ? "region" : "currency"
    });
  } catch (e) {
    displayNames = null;
  }
  const resolve = code => {
    if (!displayNames) return null;
    try {
      const resolved = displayNames.of(code);
      return resolved && resolved !== code ? resolved : null;
    } catch (e) {
      return null;
    }
  };
  const MAJOR_CURRENCIES = ["USD", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "CNY", "SGD", "HKD", "NZD", "SEK", "NOK", "DKK", "MXN", "BRL", "INR"];
  const items = codes.map(code => ({
    code,
    label: resolve(code)
  }));
  if (isCountries) {
    items.sort((a, b) => (a.label || a.code).localeCompare(b.label || b.code));
  } else {
    const rank = code => {
      const i = MAJOR_CURRENCIES.indexOf(code);
      return i === -1 ? MAJOR_CURRENCIES.length : i;
    };
    items.sort((a, b) => rank(a.code) - rank(b.code) || a.code.localeCompare(b.code));
  }
  if (codes.length <= 3) {
    const parts = items.map(it => isCountries || !it.label ? it.label || it.code : `${it.label} (${it.code})`);
    const joined = parts.length === 1 ? parts[0] : parts.length === 2 ? `${parts[0]} and ${parts[1]}` : `${parts.slice(0, -1).join(", ")}, and ${parts[parts.length - 1]}`;
    return <p>
        {name} {verb} {joined}.
      </p>;
  }
  const chipStyle = {
    display: "inline-flex",
    alignItems: "baseline",
    gap: "0.4rem",
    padding: "0.15rem 0.55rem",
    borderRadius: "0.375rem",
    border: "1px solid rgba(128, 128, 128, 0.25)",
    fontSize: "0.875rem",
    lineHeight: 1.5
  };
  const codeStyle = {
    fontFamily: "var(--font-mono, ui-monospace, monospace)",
    fontWeight: 600,
    fontSize: "0.8125rem"
  };
  const controlStyle = {
    color: "inherit",
    background: "transparent",
    border: "1px solid rgba(128, 128, 128, 0.3)",
    borderRadius: "0.5rem",
    fontSize: "0.875rem"
  };
  const renderChip = it => <span key={it.code} style={chipStyle} title={isCountries ? it.code : it.label || it.code}>
      {isCountries ? it.label || it.code : <span style={codeStyle}>{it.code}</span>}
      {!isCountries && it.label ? <span style={{
    opacity: 0.7
  }}>{it.label}</span> : null}
    </span>;
  const PREVIEW = 5;
  const collapsible = items.length > PREVIEW;
  const q = query.trim().toLowerCase();
  const filtered = q ? items.filter(it => it.code.toLowerCase().includes(q) || it.label && it.label.toLowerCase().includes(q)) : items;
  const expanded = open || q !== "";
  const visible = !collapsible ? items : expanded ? filtered : items.slice(0, PREVIEW);
  const toggle = () => {
    const next = !open;
    setOpen(next);
    if (!next) setQuery("");
  };
  return <div>
      <p>
        {name} {verb} the following {codes.length} {noun}:
      </p>

      {collapsible ? <input type="text" value={query} onChange={e => setQuery(e.target.value)} placeholder={`Filter ${noun}…`} aria-label={`Filter ${noun}`} style={{
    ...controlStyle,
    display: "block",
    width: "100%",
    maxWidth: "22rem",
    padding: "0.4rem 0.7rem",
    margin: "0 0 0.75rem"
  }} /> : null}

      <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "0.4rem"
  }}>
        {visible.map(renderChip)}
      </div>

      {q && filtered.length === 0 ? <p style={{
    opacity: 0.7,
    marginTop: "0.6rem"
  }}>
          No {noun} match “{query.trim()}”.
        </p> : null}
      {q && filtered.length > 0 ? <p style={{
    opacity: 0.6,
    fontSize: "0.8125rem",
    marginTop: "0.6rem"
  }}>
          Showing {filtered.length} of {items.length}.
        </p> : null}

      {collapsible && !q ? <button type="button" aria-expanded={open} onClick={toggle} style={{
    ...controlStyle,
    display: "inline-flex",
    alignItems: "center",
    gap: "0.4rem",
    padding: "0.35rem 0.75rem",
    marginTop: "0.75rem",
    cursor: "pointer"
  }}>
          <span aria-hidden="true" style={{
    display: "inline-block",
    transform: open ? "rotate(90deg)" : "none",
    transition: "transform 0.15s ease"
  }}>
            ›
          </span>
          {open ? "Show fewer" : `and ${items.length - PREVIEW} more`}
        </button> : null}
    </div>;
};

export const ConnectorCapabilities = ({data}) => {
  const CAPABILITIES = [{
    keys: ["three_d_secure_hosted"],
    label: "3-D Secure (hosted)",
    description: "Gr4vy-hosted 3DS authentication flow.",
    cardOnly: true
  }, {
    keys: ["three_d_secure_pass_through"],
    label: "3-D Secure (pass-through)",
    description: "Pass through 3DS data authenticated by a third party.",
    cardOnly: true
  }, {
    keys: ["partial_authorization"],
    label: "Partial authorization",
    description: "Support partial approval responses."
  }, {
    keys: ["zero_auth"],
    label: "Zero auth",
    description: "Verify a card without charging it."
  }, {
    keys: ["void"],
    label: "Void",
    description: "Cancel an authorized transaction before capture."
  }, {
    keys: ["direct_capture"],
    label: "Direct capture",
    description: "Capture a payment immediately at authorization.",
    hideWhenUnsupported: true
  }, {
    keys: ["delayed_capture"],
    label: "Delayed capture",
    description: "Authorize a payment and capture it at a later time."
  }, {
    keys: ["partial_capture"],
    label: "Partial capture",
    description: "Capture a portion of the authorized amount."
  }, {
    keys: ["over_capture"],
    label: "Over capture",
    description: "Capture more than the originally authorized amount."
  }, {
    keys: ["refunds"],
    label: "Refunds",
    description: "Refund a captured payment."
  }, {
    keys: ["partial_refunds"],
    label: "Partial refunds",
    description: "Refund a portion of the captured amount."
  }, {
    keys: ["settlement_reporting"],
    label: "Settlement reporting",
    description: "Automatic settlement and reconciliation reporting."
  }, {
    keys: ["create_session"],
    label: "Create session",
    description: "Create a connector session for client-side flows."
  }, {
    keys: ["network_tokens_default", "network_tokens_toggle"],
    label: "Network tokens",
    description: "Network-level tokenization for improved approval rates.",
    cardOnly: true
  }, {
    keys: ["digital_wallets"],
    label: "Digital wallets",
    description: "Apple Pay, Google Pay, and other wallet integrations."
  }, {
    keys: ["payment_method_tokenization", "payment_method_tokenization_toggle"],
    label: "Payment method tokenization",
    description: "Store payment methods outside of transactions."
  }, {
    keys: ["transaction_sync"],
    label: "Transaction sync",
    description: "Synchronize transaction state from the connector."
  }, {
    keys: ["create_token"],
    label: "Tokenization",
    description: "Create a token from card details collected via Secure Fields.",
    hideWhenUnsupported: true
  }, {
    keys: ["delete_token"],
    label: "Delete token",
    description: "Delete a stored token.",
    hideWhenUnsupported: true
  }, {
    keys: ["verify_credentials"],
    label: "Verify credentials",
    description: "Validate the configured credentials against the connector.",
    hideWhenUnsupported: true
  }];
  const raw = data && data.features;
  const enabled = typeof raw === "string" ? new Set(raw.split(/\s+/).filter(Boolean)) : Array.isArray(raw) ? new Set(raw) : new Set(Object.keys(raw || ({})).filter(key => raw[key]));
  const isOn = entry => entry.keys.some(key => enabled.has(key));
  const isNonCard = data && data.method && data.method !== "card";
  const renderGroup = (title, entries, supported) => {
    if (entries.length === 0) return null;
    const mark = supported ? "✓" : "✕";
    const markColor = supported ? "#16a34a" : "#9ca3af";
    return <div style={{
      marginTop: "1rem"
    }}>
        <div style={{
      fontSize: "0.75rem",
      fontWeight: 600,
      letterSpacing: "0.05em",
      textTransform: "uppercase",
      opacity: 0.6,
      marginBottom: "0.25rem"
    }}>
          {title}
        </div>
        {}
        <div role="list">
          {entries.map(entry => <div role="listitem" key={entry.label} style={{
      display: "flex",
      gap: "0.5rem",
      alignItems: "baseline",
      padding: "0.3rem 0",
      opacity: supported ? 1 : 0.7
    }}>
              <span aria-hidden="true" style={{
      color: markColor,
      fontWeight: 700,
      flexShrink: 0
    }}>
                {mark}
              </span>
              <span>
                <strong>{entry.label}</strong>
                {entry.description ? <span style={{
      opacity: 0.85
    }}> — {entry.description}</span> : null}
              </span>
            </div>)}
        </div>
      </div>;
  };
  const visible = isNonCard ? CAPABILITIES.filter(entry => !entry.cardOnly) : CAPABILITIES;
  const supported = visible.filter(isOn);
  const unsupported = visible.filter(entry => !isOn(entry) && !entry.hideWhenUnsupported);
  return <div>
      {renderGroup("Supported", supported, true)}
      {renderGroup("Not supported", unsupported, false)}
    </div>;
};

Klarna is a buy now, pay later option that lets eligible buyers split purchases into installments.

## Setup

Follow the [Stripe setup instructions](./stripe) before configuring Klarna.

After setting up your Stripe account, make sure Klarna is enabled as a payment method in Stripe.

For shared APM credentials and payment IDs, see [Stripe APM credentials](./stripe#apm-credentials).

## Capabilities

<ConnectorCapabilities data={connector} />

## Supported countries

<ConnectorRegions data={connector} kind="countries" />

## Supported currencies

<ConnectorRegions data={connector} kind="currencies" />

## Webhooks

Klarna payments and refunds can be completed asynchronously, so webhooks must be configured to update the status of transactions. Webhooks are configured in the Stripe Dashboard within Developer Settings under **Webhooks**.

The Klarna connector relies on the following Stripe webhooks:

* `charge.expired`
* `charge.refund.updated`
* `charge.refunded`
* `payment_intent.succeeded`
* `payment_intent.amount_capturable_updated`
* `payment_intent.payment_failed`
* `payment_intent.partially_funded`
* `payment_intent.canceled`
* `checkout.session.async_payment_succeeded`

## Integration

The default integration for Klarna via Stripe uses a redirect to a payments page hosted by Gr4vy.

### Redirect integration

Start by creating a new transaction with the following required fields.

<CodeGroup>
  ```csharp C# theme={"system"}
  var transaction = await client.Transactions.CreateAsync(
  	transactionCreate: new TransactionCreate()
  	{
  		Amount = 1299,
  		Currency = "USD",
  		Country = "US",
  		PaymentMethod =
  			TransactionCreatePaymentMethod.CreateRedirectPaymentMethodCreate(
  				new RedirectPaymentMethodCreate()
  				{
  					Method = "klarna",
  					Country = "US",
  					Currency = "USD",
  					RedirectUrl = "https://example.com/callback",
  				}
  			),
  	}
  );
  ```

  ```go Go theme={"system"}
  amount := int64(1299)
  currency := "USD"
  country := "US"
  method := components.RedirectPaymentMethodCreateMethodKlarna
  redirectUrl := "https://example.com/callback"

  redirectPaymentMethodCreate := components.RedirectPaymentMethodCreate{
  	Method: method,
  	Country: country,
  	Currency: currency,
  	RedirectURL: redirectUrl,
  }
  paymentMethod := components.CreateTransactionCreatePaymentMethodRedirectPaymentMethodCreate(redirectPaymentMethodCreate)

  transactionCreate := components.TransactionCreate{
  	Amount:        amount,
  	Currency:      currency,
  	Country:       &country,
  	PaymentMethod: &paymentMethod,
  }

  transaction, err := client.Transactions.Create(ctx, transactionCreate, nil, nil, nil)
  ```

  ```java Java theme={"system"}
  CreateTransactionResponse transactionResponse = gr4vyClient.transactions().create()
  	.transactionCreate(TransactionCreate.builder()
  		.amount(1299L)
  		.currency("USD")
  		.country("US")
  		.paymentMethod(TransactionCreatePaymentMethod.of(RedirectPaymentMethodCreate.builder()
  			.method(RedirectPaymentMethodCreateMethod.KLARNA)
  			.country("US")
  			.currency("USD")
  			.redirectUrl("https://example.com/callback")
  			.build()))
  		.build())
  	.call();

  Transaction transaction = transactionResponse.transaction().orElse(null);
  ```

  ```php PHP theme={"system"}
  $transactionCreate = new TransactionCreate(
  	amount: 1299,
  	currency: 'USD',
  	country: 'US',
  	paymentMethod: new RedirectPaymentMethodCreate(
  		method: 'klarna',
  		country: 'US',
  		currency: 'USD',
  		redirectUrl: 'https://example.com/callback'
  	)
  );
  $response = self::$sdk->transactions->create($transactionCreate);
  $transaction = $response->transaction;
  ```

  ```python Python theme={"system"}
  transaction: models.Transaction = client.transactions.create(
  	amount=1299,
  	currency="USD",
  	country="US",
  	payment_method={
  		"method": "klarna",
  		"country": "US",
  		"currency": "USD",
  		"redirect_url": "https://example.com/callback",
  	}
  )
  ```

  ```ts TypeScript theme={"system"}
  const transaction = await gr4vy.transactions.create({
  	amount: 1299,
  	currency: "USD",
  	country: "US",
  	paymentMethod: {
  		method: "klarna",
  		country: "US",
  		currency: "USD",
  		redirectUrl: "https://example.com/callback"
  	}
  })
  ```
</CodeGroup>

After the transaction is created, the API response includes a `payment_method.approval_url` and the status is set to `buyer_approval_pending`.

```json theme={"system"}
{
	"type": "transaction",
	"id": "ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625",
	"payment_method": {
		"type": "payment-method",
		"approval_url": "https://cdn.gr4vy.com/connectors/..."
	},
	"method": "klarna"
}
```

Redirect the buyer to the `approval_url` (open in a browser or Webview), where they can complete the payment. Once the buyer completes the payment, the transaction progresses to an `authorization_succeeded` or `capture_succeeded` state.

### Direct integration

If you intend to use your own hosted page, you can include an `integration_client` parameter set to `web`, `ios` or `android` when creating a new transaction.

<CodeGroup>
  ```csharp C# theme={"system"}
  var transaction = await client.Transactions.CreateAsync(
  	transactionCreate: new TransactionCreate()
  	{
  		Amount = 1299,
  		Currency = "USD",
  		Country = "US",
  		IntegrationClient = "ios",
  		PaymentMethod =
  			TransactionCreatePaymentMethod.CreateRedirectPaymentMethodCreate(
  				new RedirectPaymentMethodCreate()
  				{
  					Method = "klarna",
  					Country = "US",
  					Currency = "USD",
  					RedirectUrl = "myapp://callback",
  				}
  			),
  	}
  );
  ```

  ```go Go theme={"system"}
  amount := int64(1299)
  currency := "USD"
  country := "US"
  integrationClient := "ios"
  method := components.RedirectPaymentMethodCreateMethodKlarna
  redirectUrl := "myapp://callback"

  redirectPaymentMethodCreate := components.RedirectPaymentMethodCreate{
  	Method: method,
  	Country: country,
  	Currency: currency,
  	RedirectURL: redirectUrl,
  }
  paymentMethod := components.CreateTransactionCreatePaymentMethodRedirectPaymentMethodCreate(redirectPaymentMethodCreate)

  transactionCreate := components.TransactionCreate{
  	Amount:            amount,
  	Currency:          currency,
  	Country:           &country,
  	IntegrationClient: &integrationClient,
  	PaymentMethod:     &paymentMethod,
  }

  transaction, err := client.Transactions.Create(ctx, transactionCreate, nil, nil, nil)
  ```

  ```java Java theme={"system"}
  CreateTransactionResponse transactionResponse = gr4vyClient.transactions().create()
  	.transactionCreate(TransactionCreate.builder()
  		.amount(1299L)
  		.currency("USD")
  		.country("US")
  		.integrationClient("ios")
  		.paymentMethod(TransactionCreatePaymentMethod.of(RedirectPaymentMethodCreate.builder()
  			.method(RedirectPaymentMethodCreateMethod.KLARNA)
  			.country("US")
  			.currency("USD")
  			.redirectUrl("myapp://callback")
  			.build()))
  		.build())
  	.call();

  Transaction transaction = transactionResponse.transaction().orElse(null);
  ```

  ```php PHP theme={"system"}
  $transactionCreate = new TransactionCreate(
  	amount: 1299,
  	currency: 'USD',
  	country: 'US',
  	integrationClient: 'ios',
  	paymentMethod: new RedirectPaymentMethodCreate(
  		method: 'klarna',
  		country: 'US',
  		currency: 'USD',
  		redirectUrl: 'myapp://callback'
  	)
  );
  $response = self::$sdk->transactions->create($transactionCreate);
  $transaction = $response->transaction;
  ```

  ```python Python theme={"system"}
  transaction: models.Transaction = client.transactions.create(
  	amount=1299,
  	currency="USD",
  	country="US",
  	integration_client="ios",
  	payment_method={
  		"method": "klarna",
  		"country": "US",
  		"currency": "USD",
  		"redirect_url": "myapp://callback",
  	}
  )
  ```

  ```ts TypeScript theme={"system"}
  const transaction = await gr4vy.transactions.create({
  	amount: 1299,
  	currency: "USD",
  	country: "US",
  	integrationClient: "ios",
  	paymentMethod: {
  		method: "klarna",
  		country: "US",
  		currency: "USD",
  		redirectUrl: "myapp://callback"
  	}
  })
  ```
</CodeGroup>

After the transaction is created, the API response includes a `session_token`. You use this token with the [session endpoint](/reference/transactions/get-transaction-session) to retrieve the session data for the transaction and build a custom page.

```json theme={"system"}
POST /transactions/:transaction_id/session?token=:session_token

{
  "session_data": {
    "publishableKey": "some_key",
    "clientSecret": "client_secret",
    "returnUrl": "url_to_return_after_payment_completed"
  },
  "default_completion_url": "https://example.com/callback",
  "integration_client": "ios"
}
```

#### Mobile redirect integration completion

On mobile integrations you should send a GET request to the `default_completion_url` provided in the session response.

## Payment IDs

When creating the transaction, the `payment_service_transaction_id` is the client secret, and in the `extra_xids` field you can find the payment intent ID as `payment_id`.

After the transaction is approved by the user, the `payment_service_transaction_id` is updated to the payment intent ID.
