Kommo + Xero: автоматические инвойсы из воронки продаж без ручного ввода

Интеграция Kommo с Xero позволяет автоматически создавать инвойс в бухгалтерской системе в момент, когда менеджер переводит сделку в статус «Выигрыш» - без переключения между интерфейсами, без ручного копирования данных. Xero API v2 поддерживает полную двустороннюю синхронизацию: создание контакта, выставление счёта, отслеживание оплаты.

Это типичная задача для кастомных интеграций Kommo: нативный виджет Xero в маркетплейсе Kommo существует, но закрывает лишь базовый сценарий и не поддерживает кастомные поля, налоговые коды или мультивалютность.

Почему нативный виджет Xero не работает в боевых условиях

Xero имеет официальный виджет в маркетплейсе Kommo. На практике он ломается по нескольким причинам.

Проблема 1 - маппинг полей. Виджет берёт только стандартные поля сделки: сумму и название. Кастомные поля (налоговый регион, скидка, номер PO-заказа) он игнорирует. В результате бухгалтерия получает инвойс с неверными данными и правит вручную.

Проблема 2 - дублирование контактов. Виджет создаёт нового Contact в Xero при каждой сделке. Если компания уже существует в Xero с другим написанием названия, появляется дубль. Через три месяца работы бухгалтерия видит сотни дублей.

Проблема 3 - налоговые коды. Xero требует явного указания TaxType для каждой строки инвойса. Виджет подставляет дефолтный код, который не соответствует реальной ставке для конкретного клиента или страны.

Проблема 4 - мультивалютность. Если у вас клиенты в EUR и GBP, виджет не умеет передавать валюту из поля сделки - счёт всегда создаётся в валюте по умолчанию.

Кастомная интеграция через Xero API v2 закрывает все четыре проблемы.

Архитектура интеграции

Стек: Python 3.11, Kommo Webhook, Xero API v2 (OAuth 2.0 с PKCE), PostgreSQL для хранения маппинга ContactID.

Схема работы:

  1. Kommo отправляет webhook при смене статуса сделки на «Выигрыш».
  2. Микросервис получает событие, извлекает поля сделки и контакта.
  3. Поиск или создание Contact в Xero с проверкой на дубль по email.
  4. Создание Invoice с правильными строками, TaxType и валютой.
  5. Запись XeroInvoiceID обратно в кастомное поле сделки Kommo.
  6. При получении payment webhook из Xero - обновление статуса сделки.

Аутентификация Xero: OAuth 2.0 с refresh token. Xero отзывает access token каждые 30 минут. Refresh token живёт 60 дней при активном использовании. Токены хранятся в зашифрованном виде в PostgreSQL, обновляются автоматически.

import requests
from datetime import datetime, timezone
import json

XERO_TOKEN_URL = "https://identity.xero.com/connect/token"
XERO_API_BASE = "https://api.xero.com/api.xro/2.0"

def refresh_xero_token(refresh_token: str, client_id: str, client_secret: str) -> dict:
    resp = requests.post(XERO_TOKEN_URL, data={
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": client_id,
        "client_secret": client_secret,
    })
    resp.raise_for_status()
    return resp.json()

def find_or_create_contact(tenant_id: str, access_token: str, 
                            name: str, email: str) -> str:
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Xero-Tenant-Id": tenant_id,
        "Accept": "application/json",
    }
    # Поиск по email
    search = requests.get(
        f"{XERO_API_BASE}/Contacts",
        headers=headers,
        params={"where": f'EmailAddress="{email}"'}
    )
    contacts = search.json().get("Contacts", [])
    if contacts:
        return contacts[0]["ContactID"]
    
    # Создание нового контакта
    payload = {"Name": name, "EmailAddress": email}
    create = requests.post(
        f"{XERO_API_BASE}/Contacts",
        headers={**headers, "Content-Type": "application/json"},
        json={"Contacts": [payload]}
    )
    create.raise_for_status()
    return create.json()["Contacts"][0]["ContactID"]

def create_invoice(tenant_id: str, access_token: str,
                   contact_id: str, deal: dict) -> str:
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Xero-Tenant-Id": tenant_id,
        "Content-Type": "application/json",
        "Accept": "application/json",
    }
    invoice = {
        "Type": "ACCREC",
        "Contact": {"ContactID": contact_id},
        "Date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
        "DueDate": deal.get("due_date", ""),
        "CurrencyCode": deal.get("currency", "USD"),
        "Reference": deal.get("custom_po_number", ""),
        "LineItems": [{
            "Description": deal["name"],
            "Quantity": 1.0,
            "UnitAmount": float(deal["price"]),
            "TaxType": deal.get("tax_type", "NONE"),
            "AccountCode": deal.get("account_code", "200"),
        }],
        "Status": "AUTHORISED",
    }
    resp = requests.post(
        f"{XERO_API_BASE}/Invoices",
        headers=headers,
        json={"Invoices": [invoice]}
    )
    resp.raise_for_status()
    return resp.json()["Invoices"][0]["InvoiceID"]

Пошаговая реализация

Шаг 1 - регистрация Xero app. Заходите на developer.xero.com, создаёте приложение типа «Web app». Получаете Client ID и Client Secret. Указываете Redirect URI вашего микросервиса.

Шаг 2 - OAuth flow. Первичная авторизация выполняется один раз через браузер. Пользователь разрешает доступ к организации Xero. Получаете access + refresh токены, сохраняете в БД.

Шаг 3 - Kommo webhook. В настройках Kommo создаёте webhook на событие «Сделка: изменение этапа». URL - ваш микросервис. Фильтр: только переход в статус «Выигрыш».

Шаг 4 - маппинг полей. В конфигурационном файле описываете, какое кастомное поле Kommo соответствует какому параметру Xero:

{
  "tax_type_field_id": "12345",
  "currency_field_id": "67890",
  "po_number_field_id": "11111",
  "xero_invoice_id_field": "22222"
}

Шаг 5 - обратная синхронизация. Настраиваете Xero webhook на событие INVOICE с status PAID. При получении - ищете сделку по InvoiceID, меняете тег или кастомное поле в Kommo.

Шаг 6 - мониторинг. Все ошибки создания инвойса логируются и отправляются в Slack-канал бухгалтерии. Сделка при этом не блокируется - ошибка фиксируется в заметке к сделке Kommo.

Реальный кейс: SaaS-компания в Нидерландах

B2B SaaS-компания с командой 22 человека, 40-60 новых сделок в месяц. Клиенты в 12 странах с разными налоговыми ставками (NL VAT 21%, DE VAT 19%, UK VAT 20%, US без VAT).

До интеграции: финансовый менеджер тратил 3-4 часа в неделю на ручное создание инвойсов в Xero из Kommo. Ошибки в TaxType появлялись в 15% счетов - их обнаруживали при квартальной сверке.

После интеграции: инвойс создаётся автоматически через 30 секунд после закрытия сделки. Правильный налоговый код подставляется из кастомного поля «Страна клиента» через таблицу маппинга. За 6 месяцев работы - 0 ошибок TaxType.

Цифры: 3,5 часа/нед -> 0, экономия ~180 часов в год. Стоимость проекта окупилась за 8 недель.

Для кого подходит

Интеграция Kommo с Xero актуальна для компаний, у которых:

  • Более 20 новых сделок в месяц и бухгалтерия на Xero
  • Клиенты в нескольких странах с разными налоговыми ставками
  • Кастомные поля в сделках (PO-номер, проект, подразделение)
  • Требование двусторонней синхронизации: статус оплаты из Xero обратно в CRM

Xero популярен в Великобритании, Австралии, Новой Зеландии и растущем числе EU-компаний. Если ваша бухгалтерия работает в Xero - настройка воронки Kommo с автоматическим триггером на «Выигрыш» закрывает весь цикл от лида до оплаченного счёта.

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

Работает ли интеграция с мультивалютностью Xero?

Да. Xero API принимает поле CurrencyCode в каждом инвойсе. В нашей интеграции валюта берётся из кастомного поля сделки Kommo или из поля «Сумма» с выбором валюты. Для мультивалютности нужно убедиться, что в Xero включена функция Multiple Currencies (доступна на планах Established и выше). Конвертация по курсу на дату инвойса происходит автоматически на стороне Xero.

Что происходит, если Xero недоступен в момент закрытия сделки?

Микросервис использует exponential backoff: первая повторная попытка через 30 секунд, затем через 2 минуты, затем через 10 минут. Если после 5 попыток инвойс не создан, в карточку сделки Kommo добавляется заметка с ошибкой и уведомление в Slack. Сделка при этом остаётся в статусе «Выигрыш» - процесс не блокируется.

Как избежать дублирования контактов в Xero?

Поиск контакта выполняется по email-адресу из карточки Kommo. Если контакт найден - инвойс создаётся для него. Если не найден - создаётся новый с проверкой уникальности названия компании. ContactID сохраняется в кастомном поле контакта Kommo - при следующих сделках от того же клиента поиск идёт сначала по этому полю.

Можно ли автоматически менять статус сделки при оплате?

Да. Xero отправляет webhook при переходе инвойса в статус PAID. Сервис получает событие, находит сделку по сохранённому XeroInvoiceID и может добавить тег «Оплачено» или переместить сделку в финальный этап. Конкретная логика настраивается под ваш процесс.

Поддерживается ли создание повторяющихся инвойсов для подписок?

Xero поддерживает RepeatingInvoices через API. Для SaaS с помесячными или годовыми подписками можно настроить создание не разового счёта, а шаблона с расписанием. Но для подписок мы обычно рекомендуем интеграцию с Chargebee или Recurly - они более гибко управляют billing-циклами.

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

Если вы работаете в Kommo, а бухгалтерия ведётся в Xero - опишите задачу команде Exceltic.dev. Разберём вашу структуру полей, налоговую логику и предложим архитектуру интеграции. Обычно такой проект занимает 2-3 недели и не требует изменений в текущих процессах бухгалтерии.

Ещё статьи

Все →