Kommo + Braintree: платёжный gateway из воронки продаж

Braintree (PayPal) - один из немногих gateway с полноценным REST API для подписок, разовых транзакций и vault-хранения карт. Нативной интеграции с Kommo нет. Стандартный путь через Zapier не покрывает webhook-верификацию и не обновляет сделку атомарно. Правильное решение - кастомная интеграция через Braintree REST API v1.

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

Braintree не публикует готовые коннекторы для CRM. Zapier умеет создавать транзакции, но не верифицирует входящие webhook. Это создаёт две проблемы:

  • Платёж в Braintree не отражается в карточке Kommo автоматически
  • Статус транзакции (settled, failed, disputed) не попадает в воронку

Компании фиксируют платежи вручную или через выгрузки из Braintree Control Panel - это задержка от нескольких часов до суток.

Что строим

Сценарий 1 - выигранная сделка:

  1. Сделка переходит в стадию «Won» в Kommo
  2. Webhook Kommo отправляет данные клиента в наш сервис
  3. Сервис создаёт Customer в Braintree vault, генерирует payment link
  4. Ссылка добавляется Note в карточку сделки

Сценарий 2 - платёж подтверждён:

  1. Braintree отправляет webhook transaction.settled
  2. Сервис верифицирует подпись и обновляет сделку в Kommo: сумма оплаты, дата, статус

Аутентификация Braintree

Braintree использует HTTP Basic Auth: Merchant ID + Private Key в виде merchant_id:private_key, закодированных в Base64.

import base64, requests

MERCHANT_ID = "your_merchant_id"
PRIVATE_KEY = "your_private_key"
PUBLIC_KEY  = "your_public_key"

credentials = base64.b64encode(f"{PUBLIC_KEY}:{PRIVATE_KEY}".encode()).decode()

session = requests.Session()
session.headers.update({
    "Authorization": f"Basic {credentials}",
    "Content-Type": "application/json",
    "Braintree-Version": "2023-08-01",
})

BT_BASE = "https://payments.sandbox.braintreegateway.com"  # prod: payments.braintreegateway.com

Merchant ID используется в URL. Public Key + Private Key идут в Basic Auth.

Создание клиента и транзакции

Создание Customer в vault:

def create_customer(kommo_contact: dict) -> str:
    """Create Braintree customer from Kommo contact. Returns customerId."""
    payload = {
        "customer": {
            "firstName": kommo_contact.get("first_name", ""),
            "lastName":  kommo_contact.get("last_name", ""),
            "email":     kommo_contact.get("email", ""),
            "company":   kommo_contact.get("company", ""),
            "customFields": {
                "kommo_contact_id": str(kommo_contact["id"]),
            },
        }
    }
    r = session.post(f"{BT_BASE}/merchants/{MERCHANT_ID}/customers", json=payload)
    r.raise_for_status()
    return r.json()["customer"]["id"]

Создание payment link (hosted fields):

def create_payment_link(customer_id: str, amount: float, deal_id: int) -> str:
    """Generate Braintree hosted payment link. Returns URL."""
    payload = {
        "paymentLink": {
            "amount": f"{amount:.2f}",
            "currency": "EUR",
            "customerId": customer_id,
            "orderId": f"kommo-deal-{deal_id}",
            "expiresAt": "2026-12-31T23:59:59Z",
        }
    }
    r = session.post(f"{BT_BASE}/merchants/{MERCHANT_ID}/payment_links", json=payload)
    r.raise_for_status()
    return r.json()["paymentLink"]["url"]

Ссылка добавляется Note в Kommo через /api/v4/leads/{lead_id}/notes.

Верификация Braintree webhook

Braintree отправляет webhook как HTTP POST с двумя form-параметрами: bt_signature и bt_payload. Это не JSON - стандартная форма application/x-www-form-urlencoded.

import hashlib, hmac

def verify_braintree_webhook(bt_signature: str, bt_payload: str) -> bool:
    """Verify Braintree webhook. Both params come as form fields, not JSON."""
    # Braintree uses HMAC-SHA1 (not SHA-256) for webhook verification
    expected = hmac.new(
        PRIVATE_KEY.encode(),
        bt_payload.encode(),
        hashlib.sha1,
    ).hexdigest()
    # bt_signature format: "public_key|hash_value"
    parts = bt_signature.split("|")
    if len(parts) != 2 or parts[0] != PUBLIC_KEY:
        return False
    return hmac.compare_digest(parts[1], expected)

Ключевое отличие от других gateway: Braintree использует HMAC-SHA1 (не SHA-256) и передаёт сигнатуру вместе с public key через символ |.

Обработка webhook транзакции

import base64, xml.etree.ElementTree as ET
from flask import Flask, request, abort

app = Flask(__name__)

@app.route("/braintree/webhook", methods=["POST"])
def braintree_webhook():
    bt_signature = request.form.get("bt_signature", "")
    bt_payload   = request.form.get("bt_payload", "")

    if not verify_braintree_webhook(bt_signature, bt_payload):
        abort(401)

    # Payload - base64-encoded XML
    xml_data = base64.b64decode(bt_payload).decode("utf-8")
    root = ET.fromstring(xml_data)

    kind = root.findtext("kind")
    if kind != "transaction_settled":
        return "ok", 200

    transaction = root.find(".//transaction")
    amount      = transaction.findtext("amount")
    order_id    = transaction.findtext("order-id")   # "kommo-deal-{id}"
    status      = transaction.findtext("status")

    deal_id = int(order_id.replace("kommo-deal-", ""))
    update_kommo_deal(deal_id, amount, status)
    return "ok", 200

def update_kommo_deal(deal_id: int, amount: str, status: str):
    """Add payment note and update deal custom field in Kommo."""
    note_text = f"Braintree payment {status}: {amount} EUR"
    requests.post(
        f"https://YOUR.kommo.com/api/v4/leads/{deal_id}/notes",
        headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
        json={"add": [{"note_type": "common", "params": {"text": note_text}}]},
    )

Payload Braintree приходит как base64-encoded XML - не JSON. Это нестандартно для современных gateway.

Сценарий возврата средств

def refund_transaction(transaction_id: str, amount: float = None) -> dict:
    """Full or partial refund. amount=None means full refund."""
    payload = {}
    if amount:
        payload = {"transaction": {"amount": f"{amount:.2f}"}}

    r = session.post(
        f"{BT_BASE}/merchants/{MERCHANT_ID}/transactions/{transaction_id}/refund",
        json=payload,
    )
    r.raise_for_status()
    return r.json()

Refund webhook (transaction.refunded) обрабатывается аналогично - Note с суммой возврата добавляется в Kommo.

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

EU SaaS-компания с 120+ корпоративными клиентами использовала ручной ввод платежей в Kommo. Менеджеры проверяли Braintree Control Panel раз в день и копировали данные в сделки.

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

  • Статус платежа появляется в Kommo в течение 30 секунд после transaction.settled
  • Payment link генерируется автоматически при переходе сделки в «Won»
  • Refund логируется Note немедленно - без задержки до следующего рабочего дня

Время ручной работы на обработку одного платежа сократилось с ~7 минут до нуля.

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

Компании, которые уже используют Braintree как основной gateway и хотят видеть полную историю платежей в Kommo. Особенно актуально для SaaS с разовыми оплатами или гибридными моделями (подписка + разовые).

Если используете Stripe - см. Kommo + Stripe: платёжные ссылки из воронки. Для GoCardless (SEPA прямое дебетование) - отдельный гайд.

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

Чем Braintree отличается от Stripe по API?

Оба gateway предоставляют REST API. Ключевые отличия: Braintree использует Basic Auth (Public Key + Private Key), webhook верифицируется через HMAC-SHA1 с форм-параметрами, payload приходит как base64 XML. Stripe использует Bearer token, JSON payload, HMAC-SHA256.

Для новых проектов Stripe проще в интеграции. Braintree выбирают когда он уже настроен в экосистеме PayPal или нужен Vault для хранения карт.

Как хранить данные карт клиентов?

Braintree Vault хранит карты на стороне Braintree - вам не нужна PCI DSS сертификация. Вы работаете с payment_method_nonce или payment_method_token. При создании повторных транзакций используете сохранённый токен без ввода карты клиентом.

Работает ли это с подписками Braintree?

Да. Braintree Plans и Subscriptions подключаются аналогично: при выигрыше сделки создаётся Subscription, webhook subscription.charged_successfully логируется в Kommo. Dunning (неудачные попытки списания) - subscription.went_past_due добавляет задачу менеджеру.

Как тестировать без реальных платежей?

Braintree предоставляет sandbox-окружение (payments.sandbox.braintreegateway.com) с тестовыми номерами карт. Webhook из sandbox можно симулировать через Braintree Control Panel > Webhooks > Test. Для CI/CD - отдельный sandbox merchant account.

Итог

Intgration Kommo + Braintree закрывает разрыв между фактом оплаты и статусом сделки в CRM. Основные точки:

  • Аутентификация через Basic Auth (Public Key + Private Key)
  • Webhook верификация: bt_signature|public_key + HMAC-SHA1 по bt_payload
  • Payload - base64 XML, не JSON
  • Payment link создаётся автоматически при переходе в Won

Если работаете с Braintree и хотите настроить интеграцию под ваш стек - опишите задачу команде Exceltic.dev. Разберём архитектуру и оценим объём работ.

Ещё статьи

Все →