Kommo + Recurly: автоматическое создание подписок из выигранных сделок

Kommo + Recurly: автоматическое создание подписок из выигранных сделок

Recurly — enterprise-платформа управления подписками для SaaS и media-бизнесов: autmatic dunning management, proration при смене тарифа, tax compliance (Avalara интеграция), multi-currency, revenue recognition. В отличие от Chargebee или Stripe Billing, Recurly специализируется на сложных подписочных моделях с несколькими планами, add-ons и usage-based billing. Без интеграции с Kommo Won в CRM и создание подписки в Recurly — два ручных шага. С интеграцией Won -> аккаунт + подписка за секунды.

Recurly vs Chargebee vs Stripe Billing

ПараметрRecurlyChargebeeStripe Billing
Dunning managementАвтоматический, настраиваемыйАвтоматическийЧерез Smart Retries
ProrationВстроеннаяВстроеннаяЧастичная
Tax complianceAvalara нативноAvalara/TaxJarStripe Tax
Usage-based billingДаДаДа
Revenue recognitionASC 606 встроенныйЧерез интеграциюОтдельный модуль
Подходит дляEnterprise SaaS, mediaSMB–Enterprise SaaSРазработчики, marketplace

Recurly выбирают компании с 500+ подписчиками, сложной тарификацией и требованиями к revenue recognition (public companies, enterprise procurement).

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

Kommo -> Recurly:
— Won -> создать Account с данными контакта
— Won -> создать Subscription на нужный план
— Смена тарифа -> обновить Subscription (proration автоматически)
— Потеря клиента -> отменить Subscription

Recurly -> Kommo:
invoice.paid -> Note: «Recurly: платёж получен, $amount»
invoice.past_due -> Note + задача: «Счёт просрочен — связаться с клиентом»
subscription.canceled -> Note: «Подписка отменена»
subscription.reactivated -> Note: «Подписка возобновлена»

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

Base URL: https://v3.recurly.com.
Аутентификация: Basic Auth — API key как username, пустой пароль (или Bearer в заголовке).
API Version: заголовок Accept: application/vnd.recurly.v2021-02-25.

import requests
from requests.auth import HTTPBasicAuth

RECURLY_API_KEY = "your_api_key"  # из Recurly Settings -> API Credentials
RECURLY_BASE_URL = "https://v3.recurly.com"
HEADERS = {
    "Accept": "application/vnd.recurly.v2021-02-25",
    "Content-Type": "application/json",
}
AUTH = HTTPBasicAuth(RECURLY_API_KEY, "")

def create_account(code: str, email: str, first_name: str,
                   last_name: str, company: str = "") -> dict:
    # code - уникальный идентификатор (обычно email или CRM deal ID)
    payload = {
        "code": code,
        "email": email,
        "first_name": first_name,
        "last_name": last_name,
        "company": company,
    }
    resp = requests.post(
        f"{RECURLY_BASE_URL}/accounts",
        headers=HEADERS, auth=AUTH, json=payload
    )
    resp.raise_for_status()
    return resp.json()

def create_subscription(account_code: str, plan_code: str,
                        currency: str = "USD") -> dict:
    # plan_code - код плана из Recurly Plans
    payload = {
        "account": {"code": account_code},
        "plan_code": plan_code,
        "currency": currency,
    }
    resp = requests.post(
        f"{RECURLY_BASE_URL}/subscriptions",
        headers=HEADERS, auth=AUTH, json=payload
    )
    resp.raise_for_status()
    return resp.json()

def change_subscription_plan(subscription_uuid: str, plan_code: str,
                              timeframe: str = "now") -> dict:
    # timeframe: "now" | "renewal" - немедленно с proration или в следующий период
    payload = {
        "plan_code": plan_code,
        "timeframe": timeframe,
    }
    resp = requests.put(
        f"{RECURLY_BASE_URL}/subscriptions/{subscription_uuid}",
        headers=HEADERS, auth=AUTH, json=payload
    )
    resp.raise_for_status()
    return resp.json()

def cancel_subscription(subscription_uuid: str) -> dict:
    resp = requests.put(
        f"{RECURLY_BASE_URL}/subscriptions/{subscription_uuid}/cancel",
        headers=HEADERS, auth=AUTH
    )
    resp.raise_for_status()
    return resp.json()

# Маппинг тарифов Kommo -> Recurly plan codes
PLAN_MAP = {
    "starter": "plan_starter_monthly",
    "growth":  "plan_growth_monthly",
    "scale":   "plan_scale_monthly",
}

def on_deal_won(lead: dict, contact: dict):
    email = get_contact_email(contact)
    name = contact["name"].split()
    first_name = name[0] if name else ""
    last_name = " ".join(name[1:]) if len(name) > 1 else ""
    company = get_custom_field(lead, COMPANY_FIELD_ID) or ""
    plan = get_custom_field(lead, PLAN_FIELD_ID) or "starter"
    account_code = f"kommo_{lead['id']}"

    account = create_account(
        code=account_code,
        email=email,
        first_name=first_name,
        last_name=last_name,
        company=company,
    )

    plan_code = PLAN_MAP.get(plan.lower(), "plan_starter_monthly")
    sub = create_subscription(account_code=account_code, plan_code=plan_code)

    update_kommo_deal(lead["id"], {
        "recurly_account_code": account_code,
        "recurly_subscription_uuid": sub["uuid"],
    })
    create_kommo_note(lead["id"],
        f"Recurly: аккаунт создан, подписка {plan_code} активна (UUID: {sub['uuid']})")

def on_plan_upgrade(lead: dict, contact: dict, old_plan: str, new_plan: str):
    sub_uuid = get_deal_field(lead["id"], "recurly_subscription_uuid")
    if not sub_uuid:
        return
    new_plan_code = PLAN_MAP.get(new_plan.lower(), "plan_starter_monthly")
    change_subscription_plan(sub_uuid, new_plan_code, timeframe="now")
    create_kommo_note(lead["id"],
        f"Recurly: тариф изменён на {new_plan_code} (proration автоматически)")

Обработка Recurly Webhook:

import hmac, hashlib

RECURLY_WEBHOOK_KEY = "your_webhook_signing_key"

@app.route("/webhooks/recurly", methods=["POST"])
def recurly_webhook():
    # Recurly подписывает webhook через HMAC-SHA256
    sig = request.headers.get("Recurly-Signature", "")
    timestamp, received_sig = (sig.split(",") + ["", ""])[:2]
    expected = hmac.new(
        RECURLY_WEBHOOK_KEY.encode(),
        f"{timestamp}.{request.data.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, received_sig.split("=")[-1]):
        return "", 401

    payload = request.json
    event_type = payload.get("event_type")
    account_code = payload.get("account", {}).get("code", "")

    deal_id = find_deal_by_field("recurly_account_code", account_code)
    if not deal_id:
        return "", 200

    if event_type == "invoice.paid":
        amount = payload.get("invoice", {}).get("total", 0)
        create_kommo_note(deal_id,
            f"Recurly: платёж получен - ${amount/100:.2f}")

    elif event_type == "invoice.past_due":
        create_kommo_note(deal_id, "Recurly: счёт просрочен")
        create_kommo_task(deal_id,
            "Recurly: связаться с клиентом - просрочен платёж")

    elif event_type == "subscription.canceled":
        create_kommo_note(deal_id, "Recurly: подписка отменена")

    elif event_type == "subscription.reactivated":
        create_kommo_note(deal_id, "Recurly: подписка возобновлена")

    return "", 200

Dunning Management: как Recurly снижает involuntary churn

Recurly автоматически повторяет неудавшиеся платежи по настраиваемому расписанию (dunning cycles). Для Kommo-интеграции важно: когда dunning заканчивается провалом и подписка отменяется, вебхук subscription.canceled попадает в Note -> менеджер видит это в карточке и может инициировать win-back звонок.

Ретри-расписание: Recurly Dashboard -> Configuration -> Dunning Campaigns. Стандартный цикл: день 1, 3, 7, 14, 21 после первой неудачи.

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

B2B SaaS (US, 200+ подписчиков, Kommo + Recurly):

  • До: Won в Kommo -> менеджер вручную создавал аккаунт в Recurly, добавлял план, отправлял invite. 20–30 минут на каждого нового клиента. Иногда забывали указать правильный план -> клиент на неверном тарифе.
  • После: Won -> аккаунт + подписка за 10 секунд. UUID подписки сохраняется в сделке -> при апгрейде план меняется через API с автоматической proration. 0 ошибок с планом за 8 месяцев.
  • Дополнительно: invoice.past_due -> задача менеджеру в день просрочки. Involuntary churn снизился на 24% — команда реагирует до отмены подписки dunning-системой.

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

  • SaaS с 100+ активными подписчиками и сложными планами (add-ons, usage-based)
  • Компании с требованиями к revenue recognition (ASC 606) — Recurly встроен
  • Enterprise-команды с procurement-процессом и multi-currency
  • Компании где involuntary churn > 3% — dunning management критичен

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

Recurly vs Stripe Billing — когда что выбрать?

Stripe Billing: проще начать, хорошая документация, подходит для 0–500 подписок без сложной тарификации. Recurly: сложные подписки, dunning management нативно, revenue recognition, 500+ клиентов с разными планами. Для Kommo + Stripe есть отдельное руководство — архитектура аналогична, разница в возможностях платформы.

Recurly поддерживает EU VAT и tax compliance?

Да. Recurly интегрирован с Avalara для автоматического расчёта налогов в 150+ юрисдикциях включая EU VAT. Настройка: Recurly -> Integrations -> Avalara. При Won с EU-клиентом — Recurly автоматически применяет правильную ставку НДС.

Как обработать trial -> paid conversion через API?

Recurly поддерживает trial-подписки: при create_subscription с параметром trial_ends_at. После окончания trial Recurly автоматически переводит в платную. Вебхук subscription.activated (trial -> paid) — Note в Kommo. Или subscription.canceled если клиент не конвертировал.

Можно ли прикрепить payment method к аккаунту через API?

Да, через POST /accounts/{code}/billing_info с данными карты (или tokenized через Recurly.js). Для B2B обычно проще: создать аккаунт без карты -> отправить клиенту invoice -> клиент платит через Recurly-hosted страницу. Hosted invoice payment не требует PCI compliance от вашего кода.

Итого

  • Recurly API: Basic Auth (api_key + пустой пароль), Accept: application/vnd.recurly.v2021-02-25
  • Поток: create account -> create subscription -> сохранить UUID в Kommo
  • Смена плана: PUT /subscriptions/{uuid} с timeframe: "now" — proration автоматически
  • Webhook: HMAC-SHA256 через Recurly-Signature, ключевые события: invoice.paid/past_due, subscription.canceled
  • Dunning: настраивать в Dashboard, при провале -> Note в Kommo через webhook

Если вы используете Recurly и Kommo и хотите автоматизировать создание подписок при Won — опишите структуру планов и сценарии апгрейда. Exceltic.dev настроит интеграцию с proration и dunning-уведомлениями.

Ещё статьи

Все →