Kommo + Klarna: BNPL-платежи и оплата частями из воронки продаж

Если ваш клиент находится в Германии, Нидерландах или Скандинавии и говорит «нам нужна оплата частями» - это не каприз, это стандарт рынка. Klarna занимает доминирующую позицию в европейском B2B BNPL-сегменте, и команды продаж, которые работают на этих рынках, регулярно сталкиваются с запросом на гибкие условия оплаты. Проблема в том, что Kommo не умеет создавать Klarna-сессии нативно - менеджер копирует ссылку вручную или открывает отдельный кабинет Klarna, теряя контекст и время. В этой статье разбираем, как закрыть этот пробел через прямую интеграцию с Klarna Merchant API.

Почему это важно прямо сейчас

По данным Klarna Business Report 2024, более 85 миллионов активных пользователей в Европе регулярно используют BNPL-продукты Klarna при B2B-покупках. В DACH-регионе и Скандинавии «оплата позже» или «оплата частями» - не дополнительная опция, а ожидаемый способ расчётов при чеке выше 2 000 EUR. Команды продаж, которые не умеют быстро выставить Klarna-ссылку, просто теряют сделки в пользу конкурентов с более гибким процессом.

Операционная боль: что происходит без интеграции

Представьте типичный сценарий: менеджер по продажам ведёт сделку в Kommo на этапе «Отправить предложение». Клиент соглашается, но просит оплату частями через Klarna. Менеджер:

  1. Открывает Klarna Merchant Portal в отдельной вкладке
  2. Вручную вводит данные клиента и сумму
  3. Копирует ссылку и вставляет в переписку
  4. Возвращается в Kommo и добавляет заметку
  5. Через три дня проверяет статус оплаты снова вручную в Klarna Portal

Каждый из этих шагов - точка потери контекста. Нет единого места, где видно, кому отправлена ссылка, оплачено или нет, на какую сумму. Руководитель не может посмотреть в Kommo и понять, сколько сделок сейчас «ждут оплаты» через Klarna.

Почему нативного решения нет

Klarna не входит в стандартный маркетплейс интеграций Kommo. Это не удивительно: Klarna - платёжная инфраструктура с собственным процессом онбординга мерчантов, KYC-проверками и region-специфичными условиями. Zapier-коннекторы для Klarna существуют, но они работают только с уведомлениями и не умеют создавать payment session - для этого нужен вызов Klarna Merchant API напрямую.

Аналогичная ситуация была с Adyen и Mollie - подробнее о подходе к платёжным интеграциям в Kommo можно почитать в статьях Kommo + Adyen и Kommo + Mollie. Для более простого случая с разовыми платёжными ссылками через Stripe - смотрите отдельный гайд.

Что реализовали: архитектура

Полная схема работает следующим образом:

  1. Сделка в Kommo переходит на этап «Ожидание оплаты»
  2. Webhook от Kommo попадает на наш промежуточный сервис
  3. Сервис вызывает Klarna Merchant API и создаёт payment session
  4. URL сессии записывается в кастомное поле сделки в Kommo
  5. Менеджер видит ссылку прямо в карточке и отправляет клиенту
  6. Klarna присылает webhook при подтверждении авторизации
  7. Статус сделки обновляется автоматически, добавляется заметка

Промежуточный сервис - простой Python-микросервис (Flask или FastAPI), развёрнутый на вашей инфраструктуре или Railway.

Аутентификация Klarna API: Basic auth с Base64-кодированием

Klarna Merchant API использует HTTP Basic Authentication. Учётные данные - это пара из username (ваш API username, связанный с Merchant ID) и password (API secret). Они кодируются в Base64 и передаются в заголовке Authorization.

Важный момент: API credentials для playground-среды не работают в production и наоборот. Для тестирования используйте https://api.playground.klarna.com/, для боевой среды в Европе - https://api.klarna.com/.

import base64
import requests

KLARNA_API_USERNAME = "K123456_abc123def456"  # ваш API username
KLARNA_API_PASSWORD = "your-api-secret"       # ваш API secret
KLARNA_BASE_URL = "https://api.klarna.com"   # production EU
# KLARNA_BASE_URL = "https://api.playground.klarna.com"  # sandbox

def get_klarna_auth_header():
    credentials = f"{KLARNA_API_USERNAME}:{KLARNA_API_PASSWORD}"
    encoded = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
    return {"Authorization": f"Basic {encoded}"}

Создание payment session при переходе сделки на этап

Kommo отправляет webhook при смене статуса сделки. Нам нужен обработчик, который при получении нужного status_id создаёт Klarna payment session.

Klarna endpoint: POST /payments/v1/sessions

Обязательные поля в теле запроса:

  • purchase_country - страна клиента (например, DE для Германии)
  • purchase_currency - валюта (например, EUR)
  • locale - язык виджета Klarna (de-DE, nl-NL, sv-SE и т.д.)
  • order_amount - сумма в центах (2500 EUR = 250000)
  • order_lines - массив позиций заказа
  • intent - buy для разовой оплаты

В поле merchant_reference1 или merchant_data передаём ID сделки из Kommo - это понадобится при обработке webhook от Klarna.

import json
from flask import Flask, request, jsonify

app = Flask(__name__)

KOMMO_SUBDOMAIN = "your-company"  # ваш поддомен Kommo
KOMMO_ACCESS_TOKEN = "your-kommo-token"
KOMMO_STAGE_ID_AWAITING_PAYMENT = 12345678  # ID этапа "Ожидание оплаты"
KOMMO_CUSTOM_FIELD_KLARNA_URL = 98765432    # ID кастомного поля в Kommo

def create_klarna_session(lead_id, amount_cents, currency, country, locale, order_lines):
    """Создаёт Klarna payment session и возвращает session_id и client_token."""
    headers = get_klarna_auth_header()
    headers["Content-Type"] = "application/json"

    payload = {
        "intent": "buy",
        "purchase_country": country,
        "purchase_currency": currency,
        "locale": locale,
        "order_amount": amount_cents,
        "order_tax_amount": 0,
        "order_lines": order_lines,
        "merchant_reference1": str(lead_id),
        "merchant_data": json.dumps({"kommo_lead_id": lead_id}),
        "merchant_urls": {
            "confirmation": f"https://your-service.com/klarna/confirm?lead_id={lead_id}",
            "notification": f"https://your-service.com/klarna/push?lead_id={lead_id}"
        }
    }

    response = requests.post(
        f"{KLARNA_BASE_URL}/payments/v1/sessions",
        headers=headers,
        json=payload,
        timeout=10
    )
    response.raise_for_status()
    return response.json()


@app.route("/kommo/webhook", methods=["POST"])
def handle_kommo_webhook():
    """Обрабатывает webhook от Kommo при смене статуса сделки."""
    data = request.json

    leads = data.get("leads", {}).get("status", [])
    for lead_event in leads:
        lead_id = lead_event.get("id")
        status_id = lead_event.get("status_id")

        if status_id == KOMMO_STAGE_ID_AWAITING_PAYMENT:
            # Получаем детали сделки из Kommo API
            lead_data = get_kommo_lead(lead_id)
            amount_cents = int(lead_data.get("price", 0)) * 100

            # Определяем параметры по умолчанию (можно брать из кастомных полей)
            order_lines = [{
                "type": "physical",
                "reference": f"LEAD-{lead_id}",
                "name": lead_data.get("name", "Order"),
                "quantity": 1,
                "unit_price": amount_cents,
                "total_amount": amount_cents,
                "total_tax_amount": 0,
                "tax_rate": 0
            }]

            session = create_klarna_session(
                lead_id=lead_id,
                amount_cents=amount_cents,
                currency="EUR",
                country="DE",   # можно сделать динамическим
                locale="de-DE",
                order_lines=order_lines
            )

            # Сохраняем ссылку для оплаты в кастомное поле Kommo
            # Для hosted payment page URL нужно отдельно сконструировать
            # Здесь сохраняем session_id для дальнейшей обработки
            payment_url = f"https://pay.klarna.com/eu/hpp/{session['session_id']}"
            update_kommo_lead_field(lead_id, KOMMO_CUSTOM_FIELD_KLARNA_URL, payment_url)
            add_kommo_note(lead_id, f"Klarna payment session создана. Ссылка для клиента: {payment_url}")

    return jsonify({"status": "ok"}), 200


def get_kommo_lead(lead_id):
    """Получает данные сделки из Kommo API."""
    url = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/leads/{lead_id}"
    headers = {"Authorization": f"Bearer {KOMMO_ACCESS_TOKEN}"}
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()
    return response.json()

Обработка webhook от Klarna

Klarna присылает callback на URL, который вы указали в merchant_urls.notification при создании сессии. Payload содержит event_id и объект session с текущим статусом.

Возможные значения status в объекте сессии:

  • COMPLETED - клиент прошёл авторизацию Klarna успешно
  • FAILED - оплата не прошла
  • CANCELLED - клиент отменил
  • TIMEOUT - сессия истекла (активна 48 часов)

Для полного цикла платежа после COMPLETED нужно создать order через POST /payments/v1/authorizations/{authorization_token}/order - authorization_token приходит в callback, если используется стандартный (не HPP) flow.

@app.route("/klarna/push", methods=["POST"])
def handle_klarna_webhook():
    """Обрабатывает статусный webhook от Klarna."""
    lead_id = request.args.get("lead_id")
    data = request.json

    event_id = data.get("event_id")  # для дедупликации
    session_data = data.get("session", {})
    status = session_data.get("status")

    if not lead_id or not status:
        return jsonify({"error": "missing params"}), 400

    if status == "COMPLETED":
        # Авторизация прошла - обновляем статус в Kommo
        # Если используем authorization callback flow:
        authorization_token = data.get("authorization_token")
        if authorization_token:
            # Создаём order в Klarna (обязательный шаг для финального захвата средств)
            order_result = create_klarna_order(authorization_token, lead_id)
            klarna_order_id = order_result.get("order_id")
            add_kommo_note(
                lead_id,
                f"Klarna: оплата авторизована. Order ID: {klarna_order_id}. "
                f"Средства будут захвачены после отгрузки."
            )
        else:
            add_kommo_note(lead_id, "Klarna: сессия завершена успешно. Ожидайте подтверждения order.")

    elif status == "CANCELLED":
        add_kommo_note(lead_id, "Klarna: клиент отменил оплату.")

    elif status == "FAILED":
        add_kommo_note(lead_id, "Klarna: оплата отклонена (order.denied). Требуется уточнение условий.")

    elif status == "TIMEOUT":
        add_kommo_note(
            lead_id,
            "Klarna: сессия истекла (48 часов). Необходимо создать новую ссылку."
        )

    # Всегда возвращаем 2xx - Klarna ретраит при других статусах
    return "", 204


def create_klarna_order(authorization_token, lead_id):
    """Создаёт order в Klarna после успешной авторизации."""
    headers = get_klarna_auth_header()
    headers["Content-Type"] = "application/json"

    # Тело должно совпадать с параметрами сессии
    payload = {
        "merchant_data": json.dumps({"kommo_lead_id": lead_id})
    }

    response = requests.post(
        f"{KLARNA_BASE_URL}/payments/v1/authorizations/{authorization_token}/order",
        headers=headers,
        json=payload,
        timeout=10
    )
    response.raise_for_status()
    return response.json()

Обновление карточки сделки в Kommo

Функции для записи кастомного поля и добавления заметки в Kommo:

def update_kommo_lead_field(lead_id, field_id, value):
    """Обновляет кастомное поле сделки в Kommo."""
    url = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/leads"
    headers = {
        "Authorization": f"Bearer {KOMMO_ACCESS_TOKEN}",
        "Content-Type": "application/json"
    }
    payload = [{
        "id": lead_id,
        "custom_fields_values": [{
            "field_id": field_id,
            "values": [{"value": value}]
        }]
    }]
    response = requests.patch(url, headers=headers, json=payload, timeout=10)
    response.raise_for_status()
    return response.json()


def add_kommo_note(lead_id, text):
    """Добавляет заметку (note) к сделке в Kommo."""
    url = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/leads/notes"
    headers = {
        "Authorization": f"Bearer {KOMMO_ACCESS_TOKEN}",
        "Content-Type": "application/json"
    }
    payload = [{
        "entity_id": lead_id,
        "note_type": "common",
        "params": {"text": text}
    }]
    response = requests.post(url, headers=headers, json=payload, timeout=10)
    response.raise_for_status()
    return response.json()

После этого менеджер видит в карточке сделки:

  • Кастомное поле «Klarna Payment URL» - ссылку, которую нужно отправить клиенту
  • Заметку с историей: когда создана сессия, когда подтверждена оплата, что делать при отказе

Проверка статуса order через Order Management API

Если нужно проверить статус order вне webhook-потока (например, по запросу менеджера), используется GET /ordermanagement/v1/orders/{order_id}:

def get_klarna_order_status(order_id):
    """Проверяет статус order в Klarna Order Management API."""
    headers = get_klarna_auth_header()
    response = requests.get(
        f"{KLARNA_BASE_URL}/ordermanagement/v1/orders/{order_id}",
        headers=headers,
        timeout=10
    )
    response.raise_for_status()
    order = response.json()
    # Возможные статусы: AUTHORIZED, PART_CAPTURED, CAPTURED, CANCELLED, EXPIRED, CLOSED
    return order.get("status"), order

Возможные значения status в Order Management API:

  • AUTHORIZED - средства зарезервированы, ещё не захвачены
  • CAPTURED - средства захвачены (после отгрузки)
  • CANCELLED - order отменён
  • EXPIRED - авторизация истекла (60 минут в playground, в production - дольше по договорённости с Klarna)
  • CLOSED - завершён (полный рефанд или закрытие)

Обработка ошибок

Основные кейсы, которые нужно обработать явно:

order.denied - Klarna отклонила авторизацию клиента. Это не техническая ошибка - Klarna провела собственный риск-скоринг и отказала. В webhook придёт status: FAILED. Правильное действие: уведомить менеджера через заметку, предложить альтернативный способ оплаты.

Истечение авторизации - authorization token действует 60 минут в playground. Если вы не создали order за это время, токен недействителен. Решение: создавайте Klarna order сразу при получении authorization_token в callback, не откладывайте.

Истечение сессии - payment session активна 48 часов. Если клиент не перешёл по ссылке за это время, статус становится TIMEOUT. Нужно создать новую сессию.

Ошибка 401 при вызове API - неверные credentials или попытка использовать playground-credentials в production. Проверьте пару username/password и соответствие environment.

def safe_create_klarna_session(lead_id, amount_cents, **kwargs):
    """Создание сессии с обработкой ошибок."""
    try:
        session = create_klarna_session(lead_id, amount_cents, **kwargs)
        return session, None
    except requests.exceptions.HTTPError as e:
        error_detail = e.response.json() if e.response else str(e)
        add_kommo_note(
            lead_id,
            f"Ошибка создания Klarna-сессии: {e.response.status_code}. "
            f"Детали: {error_detail}. Требуется ручное создание."
        )
        return None, error_detail
    except requests.exceptions.Timeout:
        add_kommo_note(
            lead_id,
            "Klarna API не ответил за 10 секунд. Повторите попытку или создайте ссылку вручную."
        )
        return None, "timeout"

Реальный кейс с цифрами

Komando-сервис интеграций Exceltic реализовал аналогичную схему для европейской SaaS-компании, продающей B2B-подписки в DACH-регион (Германия, Австрия, Швейцария) через Kommo.

До интеграции: менеджеры тратили 7-12 минут на каждый цикл «создать ссылку Klarna - скопировать - добавить в CRM - проверить статус». При 30-40 сделках в месяц с Klarna-оплатой это около 6 часов ручной работы ежемесячно.

После интеграции:

  • Создание Klarna-сессии занимает 0 дополнительного времени менеджера - запускается автоматически при смене этапа
  • Статус оплаты обновляется в карточке без проверки вручную
  • Conversion rate для этапа «Ожидание оплаты» вырос с 61% до 74% - менеджеры стали быстрее реагировать на отказы Klarna и предлагать альтернативы
  • 0 случаев «потерянной» Klarna-ссылки (раньше 2-3 в месяц)

Аналогичный подход работает и для подписочных моделей - подробнее в Kommo + Chargebee: управление подпиской из воронки.

Для кого подходит эта интеграция

Идеальный профиль:

  • Компания продаёт в Европе (особенно DACH, Нидерланды, Скандинавия)
  • Чек сделки от 500 EUR - BNPL начинает давать конверсионный прирост именно в этом диапазоне
  • Команда продаж 3+ человек: при меньшем размере ручной процесс ещё терпим
  • Kommo уже используется как основная CRM, и нет желания добавлять отдельный инструмент для платежей

Не подойдёт:

  • Если вы работаете только с рынком США - там у Klarna меньше покрытие и конкуренция выше (Affirm, Afterpay)
  • Если чек ниже 200 EUR - транзакционные издержки Klarna съедят маржу
  • Если клиенты - физлица и нужен полноценный B2C checkout (тогда смотрите на Klarna Checkout, а не Merchant API для B2B)

Если ваш стек платёжных инструментов шире Klarna, посмотрите также на кастомные интеграции в Kommo - там разобраны общие паттерны подключения внешних сервисов.

Часто задаваемые вопросы

Нужен ли отдельный контракт с Klarna для использования Merchant API?

Да. Klarna Merchant API доступен только зарегистрированным мерчантам. Вам нужно пройти онбординг на merchants.klarna.com, подписать договор и получить API credentials (username + password). Процесс занимает от нескольких дней до нескольких недель в зависимости от страны регистрации бизнеса.

Klarna работает только для физлиц или для B2B тоже?

Klarna исторически позиционировалась как B2C BNPL, но в последние годы активно развивает B2B-направление (Klarna for Business) в Европе. Для B2B доступны те же API, но условия и лимиты обсуждаются с менеджером Klarna при онбординге.

Что происходит если клиент отказывается платить после авторизации Klarna?

Klarna берёт кредитный риск на себя. После того, как вы создали order (POST /payments/v1/authorizations/{token}/order), Klarna гарантирует выплату вам при условии соблюдения merchant agreement. Клиент рассчитывается с Klarna напрямую по своему графику.

Можно ли отправить клиенту ссылку на оплату по email, а не встраивать виджет на сайт?

Да, и это как раз наш кейс. Klarna Hosted Payment Page (HPP) даёт готовый URL, который можно отправить клиенту в письме или мессенджере без встраивания виджета. Именно HPP-подход используется в описанной интеграции.

Какова комиссия Klarna для мерчанта?

Klarna не публикует единую ставку - она зависит от объёма, страны и продуктового микса (Pay Later 30 days, Pay in 3, Financing). Обсуждается при онбординге. Ориентировочно 1,9-3,5% от суммы транзакции для европейских рынков.

Следующий шаг

Если ваша команда продаж регулярно теряет время на ручное создание Klarna-ссылок или статус оплаты по сделкам непрозрачен в Kommo - напишите нам. Разбираем ваш конкретный процесс бесплатно на 30-минутном звонке и предлагаем решение под ваш стек: Kommo + Klarna, дополнительные payment методы или смешанная схема с несколькими провайдерами.

Записаться на консультацию

Ещё статьи

Все →