Kommo + GoCardless: автоматическое прямое дебетование из воронки продаж

Kommo + GoCardless: автоматическое прямое дебетование из воронки продаж

GoCardless — европейская платформа для прямого дебетования: SEPA Direct Debit (EU), BACS (UK), ACH (US). В отличие от Stripe или Mollie, GoCardless специализируется на регулярных платежах через банковские счета — без комиссий за карточные транзакции и с более высоким success rate для подписочных бизнесов. Без интеграции с Kommo менеджер создаёт мандат вручную после Won. С интеграцией Won автоматически инициирует процесс получения мандата — клиент получает ссылку для авторизации списаний прямо в минуту после закрытия сделки.

GoCardless vs Stripe для SaaS-подписок в EU

GoCardless (SEPA DD):
— Фиксированная комиссия: €0.20–0.60 за транзакцию (не % от суммы)
— Нет лимита на % — на крупных подписках значительно дешевле карт
— Мандат даётся один раз -> повторные списания без участия клиента
— Success rate 97–99% (карты: 85–92% из-за истечений, блокировок)
— Идеально для B2B SaaS с ежемесячными/ежеквартальными invoice

Stripe (карточные платежи):
— % от транзакции (2.9% + $0.30 или локальные EU тарифы)
— Карты истекают, блокируются -> involuntary churn
— Мгновенная оплата (vs 2–5 рабочих дней для DD)

Для кастомных интеграций Kommo с европейскими SaaS GoCardless часто предпочтительнее именно из-за структуры комиссий и надёжности.

Что синхронизируется

Kommo -> GoCardless:
— Won -> создать Customer в GoCardless с данными из сделки
— Won -> создать Redirect Flow (ссылка для авторизации мандата клиентом)
— Won -> отправить ссылку на мандат клиенту (через Note или email)
— После получения мандата -> создать Subscription (план, сумма, интервал)

GoCardless -> Kommo:
payment.paid -> Note: «GoCardless: платёж получен, €{amount}»
payment.failed -> Note + задача менеджеру: «Платёж не прошёл — связаться с клиентом»
mandate.cancelled -> Note + задача: «Мандат отозван — переоформить»
subscription.cancelled -> Note: «Подписка отменена»

GoCardless API: ключевые запросы

Base URL: https://api.gocardless.com.
Аутентификация: Authorization: Bearer {access_token}.
Access Token: GoCardless Dashboard -> Developers -> Create Access Token.
Заголовок версии API: GoCardless-Version: 2015-07-06 (обязателен).

import requests

GC_TOKEN = "your_access_token"
GC_BASE_URL = "https://api.gocardless.com"
GC_HEADERS = {
    "Authorization": f"Bearer {GC_TOKEN}",
    "GoCardless-Version": "2015-07-06",
    "Content-Type": "application/json",
}

def create_customer(name: str, email: str, company_name: str = "") -> dict:
    payload = {
        "customers": {
            "email": email,
            "given_name": name.split()[0] if name else "",
            "family_name": " ".join(name.split()[1:]) if len(name.split()) > 1 else "",
            "company_name": company_name,
        }
    }
    resp = requests.post(f"{GC_BASE_URL}/customers", headers=GC_HEADERS, json=payload)
    resp.raise_for_status()
    return resp.json()["customers"]

def create_redirect_flow(customer_id: str, description: str,
                         success_redirect_url: str, session_token: str) -> dict:
    # Создаёт ссылку для авторизации мандата клиентом
    payload = {
        "redirect_flows": {
            "description": description,
            "session_token": session_token,  # уникальная строка для сессии
            "success_redirect_url": success_redirect_url,
            "prefilled_customer": {"id": customer_id},
        }
    }
    resp = requests.post(f"{GC_BASE_URL}/redirect_flows", headers=GC_HEADERS, json=payload)
    resp.raise_for_status()
    return resp.json()["redirect_flows"]

def complete_redirect_flow(redirect_flow_id: str, session_token: str) -> dict:
    # Завершить redirect flow после возврата клиента, получить mandate_id
    payload = {"data": {"session_token": session_token}}
    resp = requests.post(
        f"{GC_BASE_URL}/redirect_flows/{redirect_flow_id}/actions/complete",
        headers=GC_HEADERS,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json()["redirect_flows"]

def create_subscription(mandate_id: str, amount_cents: int,
                        currency: str, interval_unit: str,
                        name: str) -> dict:
    # interval_unit: "monthly" | "weekly" | "yearly"
    # amount_cents: сумма в центах/евроцентах (49.00 EUR = 4900)
    payload = {
        "subscriptions": {
            "amount": amount_cents,
            "currency": currency,
            "interval_unit": interval_unit,
            "name": name,
            "links": {"mandate": mandate_id},
        }
    }
    resp = requests.post(f"{GC_BASE_URL}/subscriptions", headers=GC_HEADERS, json=payload)
    resp.raise_for_status()
    return resp.json()["subscriptions"]

def on_deal_won(lead: dict, contact: dict):
    import uuid
    email = get_contact_email(contact)
    name = contact["name"]
    company = get_custom_field(lead, COMPANY_FIELD_ID) or ""
    amount = lead.get("price", 0)
    plan = get_custom_field(lead, PLAN_FIELD_ID) or "Growth"

    # Создать Customer
    customer = create_customer(name, email, company)
    customer_id = customer["id"]

    # Создать Redirect Flow - ссылка для клиента
    session_token = str(uuid.uuid4())
    flow = create_redirect_flow(
        customer_id=customer_id,
        description=f"Авторизация списаний - {plan} план",
        success_redirect_url="https://yourapp.com/payment/confirmed",
        session_token=session_token,
    )

    # Сохранить данные в Kommo
    update_kommo_deal(lead["id"], {
        "gc_customer_id": customer_id,
        "gc_redirect_flow_id": flow["id"],
        "gc_session_token": session_token,
    })

    create_kommo_note(lead["id"],
        f"GoCardless: мандат ожидает авторизации\n"
        f"Ссылка для клиента: {flow['redirect_url']}")

Webhook после авторизации клиентом (redirect_flow.completed):

@app.route("/webhooks/gocardless/redirect", methods=["GET"])
def gc_redirect_complete():
    # Клиент вернулся после авторизации мандата
    redirect_flow_id = request.args.get("redirect_flow_id")
    deal_id = find_deal_by_field("gc_redirect_flow_id", redirect_flow_id)
    if not deal_id:
        return "OK", 200

    session_token = get_deal_field(deal_id, "gc_session_token")
    flow = complete_redirect_flow(redirect_flow_id, session_token)
    mandate_id = flow["links"]["mandate"]

    amount = get_deal_amount(deal_id)
    sub = create_subscription(
        mandate_id=mandate_id,
        amount_cents=int(amount * 100),
        currency="EUR",
        interval_unit="monthly",
        name=f"Подписка #{deal_id}",
    )

    update_kommo_deal(deal_id, {"gc_mandate_id": mandate_id,
                                "gc_subscription_id": sub["id"]})
    create_kommo_note(deal_id,
        f"GoCardless: мандат получен, подписка создана (ID: {sub['id']})")
    return "OK", 200

Обработка GoCardless Webhook (платёжные события):

import hmac, hashlib

GC_WEBHOOK_SECRET = "your_webhook_secret"

@app.route("/webhooks/gocardless", methods=["POST"])
def gc_webhook():
    # Верификация подписи
    sig = request.headers.get("Webhook-Signature", "")
    expected = hmac.new(
        GC_WEBHOOK_SECRET.encode(), request.data, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, sig):
        return "", 498

    payload = request.json
    for event in payload.get("events", []):
        resource_type = event.get("resource_type")  # "payments", "mandates", "subscriptions"
        action = event.get("action")               # "paid", "failed", "cancelled" etc.
        links = event.get("links", {})

        if resource_type == "payments":
            payment_id = links.get("payment")
            mandate_id = links.get("mandate")
            deal_id = find_deal_by_field("gc_mandate_id", mandate_id)
            if not deal_id:
                continue
            amount = event.get("details", {}).get("amount", 0) / 100

            if action == "paid":
                create_kommo_note(deal_id,
                    f"GoCardless: платёж получен - €{amount:.2f}")

            elif action == "failed":
                reason = event.get("details", {}).get("description", "unknown")
                create_kommo_note(deal_id,
                    f"GoCardless: платёж не прошёл - {reason}")
                create_kommo_task(deal_id,
                    "GoCardless: связаться с клиентом - проблема с платежом")

        elif resource_type == "mandates" and action == "cancelled":
            mandate_id = links.get("mandate")
            deal_id = find_deal_by_field("gc_mandate_id", mandate_id)
            if deal_id:
                create_kommo_note(deal_id, "GoCardless: мандат отозван клиентом")
                create_kommo_task(deal_id,
                    "GoCardless: переоформить мандат или выставить счёт альтернативно")

    return "", 204

Sandbox для тестирования

GoCardless предоставляет sandbox-среду (https://api-sandbox.gocardless.com) — полная копия production API. Тестовые реквизиты банковского счёта генерирует Dashboard. Webhook можно тестировать через ngrok или GoCardless -> Developers -> Webhook endpoints -> Send test event.

Реальный кейс

EU SaaS-компания (Нидерланды, 40–60 новых клиентов в месяц, Kommo + GoCardless):

  • До: после Won менеджер вручную отправлял GoCardless-ссылку через email. Задержка 1–2 часа. 30% клиентов не завершали авторизацию мандата — follow-up вручную.
  • После: Won -> GoCardless ссылка в Note за 5 секунд + автоматический email клиенту. Конверсия в завершённый мандат выросла до 89% (было 67%) — ссылка приходит пока клиент ещё в «режиме покупки».
  • Дополнительно: payment.failed -> задача менеджеру в тот же день. Involuntary churn снизился на 31% — команда реагирует до конца billing-периода.

Для кого актуально

  • EU SaaS и B2B-компании с ежемесячными/ежеквартальными платежами
  • Команды с высоким MRR на клиента ($200+) — фиксированная комиссия GoCardless выгоднее % от Stripe
  • Компании в UK/Нидерланды/Германия/Франция — SEPA и BACS нативны
  • 20+ активных подписок — при меньшем объёме ручное управление ещё терпимо

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

GoCardless vs Stripe для EU SaaS — когда что выбрать?

GoCardless: ежемесячные подписки B2B в EU, высокий средний чек ($200–5000/мес), нет необходимости в мгновенных платежах. Stripe: одноразовые платежи, e-commerce, нужна мгновенная оплата, международный рынок за пределами EU. Можно использовать оба: GoCardless для подписок, Stripe для апгрейдов и одноразовых платежей.

Как работает SEPA Direct Debit — сколько дней до списания?

После авторизации мандата первое списание занимает 2–3 рабочих дня (уведомление клиенту + T+2/T+3). Повторные — стандартно T+2. GoCardless автоматически уведомляет клиента перед каждым списанием (обязательно по SEPA-правилам, обычно за 3+ дня).

Можно ли сделать мандат без перехода клиента по ссылке?

Для SEPA DD — нет, клиент должен авторизовать мандат. Альтернатива: SEPA Instant Credit Transfer (не GoCardless) или использовать GoCardless Instant Bank Pay (Faster Payments, UK только). Для EU самый надёжный способ — redirect flow с брендированной страницей GoCardless.

GoCardless поддерживает несколько валют?

Да: EUR (SEPA), GBP (BACS), USD (ACH), SEK, DKK, AUD, NZD, CAD. Каждая валюта требует отдельного Scheme в аккаунте. Российский рубль и большинство EM-валют не поддерживаются.

Итого

  • GoCardless API: Authorization: Bearer {token} + GoCardless-Version: 2015-07-06
  • Поток: create customer -> create redirect flow -> клиент авторизует -> complete flow -> create subscription
  • Webhook: HMAC-SHA256 через Webhook-Signature, события в массиве events[]
  • Ключевые события: payment.paid, payment.failed, mandate.cancelled
  • Sandbox: https://api-sandbox.gocardless.com — полная копия production
  • Преимущество vs карты: фиксированная комиссия + 97–99% success rate для подписок

Если вы используете GoCardless и Kommo и хотите автоматизировать получение мандатов при Won — опишите структуру тарифов и billing-интервалы. Exceltic.dev настроит redirect flow и обработку платёжных событий.

Ещё статьи

Все →