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

Square - платёжная платформа для малого и среднего бизнеса: терминалы, онлайн-платежи, инвойсы, подписки. Популярна в США, Канаде, Австралии, Великобритании, Японии. Для B2B SaaS и сервисных компаний Square предлагает Payment Links и Invoices API - выставить счёт и принять оплату без физического терминала. Кастомная интеграция с Kommo позволяет создавать платёжную ссылку прямо из карточки сделки и автоматически переводить сделку в Closed Won после получения оплаты.

Square Payments API работает на основе OAuth 2.0 и nonce-токенов (одноразовых токенов из Web Payments SDK). Для серверных операций - Payment Links API: создать ссылку, отправить клиенту, получить уведомление о платеже через webhook.

Payment Link (Square) - одноразовая или многоразовая страница оплаты с фиксированной суммой и описанием. Создаётся через API, не требует сайта или POS-терминала.

Проблема: ручное выставление счетов

Типичный процесс без интеграции: сделка закрылась в Kommo -> менеджер открывает Square Dashboard -> вручную создаёт инвойс или Payment Link -> копирует ссылку в Kommo -> ждёт оплаты -> вручную отмечает сделку как оплаченную.

Этот процесс занимает 10-15 минут на каждую сделку. При 20+ сделках в месяц - 3-5 часов на рутину. Оплаченные сделки остаются в статусе “ожидает оплаты” до ручного обновления.

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

Kommo: сделка переходит в этап "Выставить счёт"
  -> Kommo webhook: leads.status.changed
  -> Ваш сервер

Ваш сервер
  -> Square API: создать Payment Link
     {amount: deal.budget, note: deal.name, kommo_lead_id: deal.id}
  -> Kommo API: добавить ссылку в карточку сделки как примечание

Клиент оплачивает -> Square
  -> Square webhook: payment.completed
     {order.reference_id: kommo_lead_id}
  -> Ваш сервер
  -> Kommo API: перевести сделку в Closed Won
  -> Kommo API: обновить custom field "Оплачено"
import requests, uuid, os
from flask import Flask, request, jsonify

app = Flask(__name__)

SQUARE_ACCESS_TOKEN  = os.environ["SQUARE_ACCESS_TOKEN"]
SQUARE_LOCATION_ID   = os.environ["SQUARE_LOCATION_ID"]
SQUARE_WEBHOOK_SIG   = os.environ["SQUARE_WEBHOOK_SIGNATURE_KEY"]

KOMMO_SUBDOMAIN      = os.environ["KOMMO_SUBDOMAIN"]  # mycompany
KOMMO_TOKEN          = os.environ["KOMMO_ACCESS_TOKEN"]

INVOICE_STAGE_ID     = int(os.environ["KOMMO_INVOICE_STAGE_ID"])
CLOSED_WON_STAGE_ID  = int(os.environ["KOMMO_CLOSED_WON_STAGE_ID"])

SQUARE_BASE = "https://connect.squareup.com/v2"
SQUARE_HDR  = {
    "Authorization": f"Bearer {SQUARE_ACCESS_TOKEN}",
    "Content-Type":  "application/json",
    "Square-Version": "2024-05-15",
}

KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR  = {
    "Authorization": f"Bearer {KOMMO_TOKEN}",
    "Content-Type":  "application/json",
}

def get_lead(lead_id: int) -> dict:
    r = requests.get(
        f"{KOMMO_BASE}/leads/{lead_id}",
        headers=KOMMO_HDR,
        params={"with": "contacts,custom_fields_values"},
    )
    return r.json()

def create_payment_link(amount_usd: float, note: str, reference_id: str) -> str:
    # Square работает в центах
    amount_cents = int(amount_usd * 100)
    payload = {
        "idempotency_key": str(uuid.uuid4()),
        "quick_pay": {
            "name":        note,
            "price_money": {"amount": amount_cents, "currency": "USD"},
            "location_id": SQUARE_LOCATION_ID,
        },
        "checkout_options": {
            "redirect_url": "",
            "ask_for_shipping_address": False,
        },
        "pre_populated_data": {},
        "payment_note": reference_id,  # kommo_lead_id для обратной ссылки
        "order": {
            "reference_id": reference_id,  # вернётся в webhook
        },
    }
    r = requests.post(f"{SQUARE_BASE}/online-checkout/payment-links", headers=SQUARE_HDR, json=payload)
    r.raise_for_status()
    return r.json()["payment_link"]["url"]

def add_note_to_lead(lead_id: int, text: str):
    requests.post(
        f"{KOMMO_BASE}/notes",
        headers=KOMMO_HDR,
        json=[{
            "entity_id":   lead_id,
            "entity_type": "leads",
            "note_type":   "common",
            "params":      {"text": text},
        }],
    )

@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
    data = request.json or {}
    for lead_data in data.get("leads", {}).get("status", []):
        lead_id    = lead_data.get("id")
        new_status = lead_data.get("status_id")

        if new_status != INVOICE_STAGE_ID:
            continue

        lead = get_lead(lead_id)
        budget = lead.get("price", 0) or 0
        name   = lead.get("name", f"Сделка #{lead_id}")

        if budget <= 0:
            add_note_to_lead(lead_id, "Square: сумма сделки не указана. Создайте Payment Link вручную.")
            continue

        link = create_payment_link(
            amount_usd=float(budget),
            note=name,
            reference_id=str(lead_id),
        )
        add_note_to_lead(lead_id, f"Square Payment Link создан: {link}")

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

Реализация: webhook при успешной оплате

import hmac, hashlib, base64

def verify_square_signature(body: bytes, signature: str, sig_key: str, url: str) -> bool:
    # Square HMAC-SHA256: base64(hmac(url + body, key))
    msg     = (url + body.decode("utf-8")).encode("utf-8")
    secret  = sig_key.encode("utf-8")
    digest  = hmac.new(secret, msg, hashlib.sha256).digest()
    computed = base64.b64encode(digest).decode()
    return hmac.compare_digest(computed, signature)

@app.route("/webhooks/square", methods=["POST"])
def square_webhook():
    sig      = request.headers.get("x-square-hmacsha256-signature", "")
    full_url = request.url  # должен совпадать с URL в Square Developer Dashboard

    if not verify_square_signature(request.data, sig, SQUARE_WEBHOOK_SIG, full_url):
        return jsonify({"error": "invalid signature"}), 401

    event = request.json or {}
    if event.get("type") != "payment.completed":
        return jsonify({"status": "ignored"}), 200

    payment   = event.get("data", {}).get("object", {}).get("payment", {})
    order_ref = payment.get("order_id", "")

    # reference_id хранится в order, нужен отдельный запрос
    order_id   = payment.get("order_id", "")
    if order_id:
        r_order  = requests.get(f"{SQUARE_BASE}/orders/{order_id}", headers=SQUARE_HDR)
        order    = r_order.json().get("order", {})
        kommo_id = order.get("reference_id", "")
    else:
        kommo_id = ""

    if not kommo_id:
        return jsonify({"status": "no_lead_id"}), 200

    amount_cents = payment.get("amount_money", {}).get("amount", 0)
    amount_usd   = amount_cents / 100.0

    # Перевести сделку в Closed Won
    requests.patch(
        f"{KOMMO_BASE}/leads/{kommo_id}",
        headers=KOMMO_HDR,
        json={"status_id": CLOSED_WON_STAGE_ID},
    )

    # Добавить примечание об оплате
    add_note_to_lead(
        int(kommo_id),
        f"Square: получена оплата ${amount_usd:.2f}. Payment ID: {payment.get('id', '')}",
    )

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

Настройка Square Developer Portal

  1. Перейти на https://developer.squareup.com/ -> Applications -> создать App
  2. Получить Access Token (Production или Sandbox для тестирования)
  3. Получить Location ID: Square Dashboard -> Locations
  4. Webhooks: Developer Portal -> Webhooks -> Add endpoint
    • URL: https://your-server.com/webhooks/square
    • Events: payment.completed, payment.failed, order.updated
  5. Скопировать Signature Key из настроек webhook

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

Консалтинговое агентство, 6 менеджеров, средний чек $2 400, 25 сделок в месяц. Square использовался как основной инструмент сбора платежей, Kommo - для ведения воронки. Без интеграции: менеджеры тратили 2-3 часа в неделю на создание Payment Links и обновление статусов.

После интеграции: Payment Link создаётся автоматически при переводе сделки в этап “Выставить счёт”. После оплаты сделка переходит в Closed Won без участия менеджера. Экономия: ~2.5 часа в неделю на команду.

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

Сервисные компании и B2B SaaS в США, Канаде, Австралии, работающие через Square как основной платёжный инструмент. Особенно если средний чек $500-5000 и команда продаж 3-15 человек.

Если компания использует другой платёжный gateway - аналогичный подход описан для Kommo + Stripe и Kommo + Paddle.

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

Поддерживает ли Square рекуррентные платежи (подписки)?

Да. Square Subscriptions API: POST /v2/subscriptions с plan_variation_id и card_id. Карту клиента нужно сохранить предварительно через Web Payments SDK. Для B2B SaaS с ежемесячными платежами - полноценная альтернатива Stripe Billing.

В каких странах работает Square?

США, Канада, Австралия, Великобритания, Япония, Ирландия, Франция, Испания. Для ЕС в целом Square менее распространён - там лучше подойдут Stripe, Mollie или Checkout.com.

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

Square предоставляет Sandbox окружение с отдельными API-ключами. Sandbox Access Token и Location ID получаются в Developer Dashboard отдельно от Production. Тестовые карты: 4111 1111 1111 1111 (Visa), CVV любые 3 цифры.

Итог

Kommo + Square - автоматизация приёма платежей из воронки:

  • Kommo webhook leads.status.changed -> создать Square Payment Link
  • order.reference_id = kommo_lead_id для корреляции
  • Square webhook payment.completed -> Closed Won + примечание
  • HMAC-SHA256 верификация: base64(hmac(url + body, sig_key))
  • Идемпотентность: UUID idempotency_key при создании платежа

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

Ещё статьи

Все →