Kommo + Adyen: enterprise-платежи из воронки продаж с автоматическим учётом
Adyen — enterprise payment platform которую используют Uber, Spotify, McDonald’s, Booking.com: единый gateway для онлайн-платежей, in-store (POS), in-app и marketplace payouts. Ключевые преимущества перед Stripe или Recurly: глобальное acquiring (прямые соглашения с платёжными системами без посредников), встроенная сверка через Adyen Data для финансовых отчётов, marketplace split payments. Для B2B компаний с enterprise-клиентами Adyen часто является стандартом — не потому что проще, а потому что контракт требует. Без интеграции с Kommo Won: платёжная ссылка создаётся вручную, менеджер не видит статус оплаты в CRM.
Adyen vs Stripe vs Mollie для enterprise B2B
| Параметр | Adyen | Stripe | Mollie |
|---|---|---|---|
| Модель acquiring | Прямая (own acquiring) | Через партнёров | Через партнёров |
| Interchange++ pricing | Да | Нет | Нет |
| Marketplace payments | Нативно | Stripe Connect | Нет |
| In-store (POS) | Да (Adyen Terminal) | Stripe Terminal | Нет |
| Минимум оборот | ~$1M/год | Нет | Нет |
| Сверка/отчётность | Adyen Data (встроенная) | Через дашборд | Через дашборд |
| EU-ориентация | Да (Amsterdam) | Да (Dublin) | Да (Netherlands) |
Adyen выбирают компании с оборотом от $1M/год где важна прямая межбанковская комиссия (interchange++) и единая платформа для всех каналов продаж.
Архитектура: Kommo + Adyen
Kommo Won -> Python handler -> Adyen Payment Links API -> ссылка в Note
Adyen Webhook -> Python handler -> Kommo Note / Deal Stage
Adyen не имеет коннектора для Kommo. Интеграция через Adyen API (Management API + Checkout API) и webhooks.
Аутентификация: Adyen API Key
Adyen использует заголовок X-API-Key. API Key создаётся в Adyen Customer Area -> Developers -> API credentials -> Create new credential.
import requests
import hmac, hashlib, base64
ADYEN_API_KEY = "your_api_key"
ADYEN_MERCHANT = "YourMerchantAccount"
ADYEN_BASE = "https://checkout-test.adyen.com/checkout/v71"
# Production: https://checkout-live.adyen.com/checkout/v71
ADYEN_HEADERS = {
"X-API-Key": ADYEN_API_KEY,
"Content-Type": "application/json",
}
ADYEN_HMAC_KEY = "your_hmac_key_hex" # из Adyen Customer Area -> Webhooks
Создание платёжной ссылки при Won
Adyen Payment Links позволяет создать ссылку на hosted checkout без PCI-ответственности на вашей стороне:
from datetime import datetime, timezone, timedelta
def create_adyen_payment_link(amount_cents: int, currency: str,
description: str, reference: str,
email: str = "") -> dict:
# reference - уникальный идентификатор, используем Kommo deal ID
expires_at = (
datetime.now(timezone.utc) + timedelta(days=7)
).strftime("%Y-%m-%dT%H:%M:%S+00:00")
payload = {
"merchantAccount": ADYEN_MERCHANT,
"amount": {
"currency": currency,
"value": amount_cents,
},
"reference": reference,
"description": description,
"expiresAt": expires_at,
"returnUrl": "https://yoursite.com/payment-complete",
}
if email:
payload["shopperEmail"] = email
resp = requests.post(
f"{ADYEN_BASE}/paymentLinks",
headers=ADYEN_HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
def on_kommo_deal_won(lead: dict, contact: dict):
price = int((lead.get("price") or 0) * 100) # cents
currency = get_custom_field(lead, CURRENCY_FIELD_ID) or "EUR"
reference = f"kommo-{lead['id']}"
description = lead.get("name", "")
email = get_contact_email(contact)
link_data = create_adyen_payment_link(
amount_cents=price,
currency=currency,
description=description,
reference=reference,
email=email,
)
link_url = link_data.get("url", "")
link_id = link_data.get("id", "")
save_to_kommo_deal(lead["id"], {
"adyen_payment_link_id": link_id,
"adyen_reference": reference,
})
create_kommo_note(
lead["id"],
f"Adyen: платёжная ссылка создана -> {link_url} (истекает через 7 дней)",
)
Webhook: Adyen -> Kommo
Adyen webhooks подписываются через HMAC-SHA256. Настройка: Customer Area -> Developers -> Webhooks -> Standard webhook.
def verify_adyen_hmac(payload: dict, hmac_key_hex: str,
received_hmac: str) -> bool:
# Adyen HMAC verification
fields = [
"pspReference", "originalReference", "merchantAccountCode",
"merchantReference", "value", "currency", "eventCode", "success",
]
hmac_key = bytes.fromhex(hmac_key_hex)
data = ":".join(str(payload.get(f, "")) for f in fields)
computed = base64.b64encode(
hmac.new(hmac_key, data.encode("utf-8"), hashlib.sha256).digest()
).decode()
return hmac.compare_digest(computed, received_hmac)
@app.route("/webhooks/adyen", methods=["POST"])
def adyen_webhook():
body = request.json
for item in body.get("notificationItems", []):
notification = item.get("NotificationRequestItem", {})
received_hmac = notification.get("additionalData", {}).get("hmacSignature", "")
if not verify_adyen_hmac(notification, ADYEN_HMAC_KEY, received_hmac):
return "[accepted]", 401
event_code = notification.get("eventCode", "")
reference = notification.get("merchantReference", "")
success = notification.get("success") == "true"
lead_id = find_kommo_deal_by_custom_field("adyen_reference", reference)
if not lead_id:
continue
if event_code == "AUTHORISATION" and success:
amount = notification.get("amount", {})
value_str = f"{amount.get('value', 0) / 100:.2f} {amount.get('currency', '')}"
create_kommo_note(lead_id,
f"Adyen: платёж авторизован - {value_str}")
elif event_code == "CAPTURE" and success:
amount = notification.get("amount", {})
value_str = f"{amount.get('value', 0) / 100:.2f} {amount.get('currency', '')}"
create_kommo_note(lead_id,
f"Adyen: платёж захвачен (captured) - {value_str}")
move_kommo_deal_to_stage(lead_id, PAID_STAGE_ID)
elif event_code == "REFUND" and success:
create_kommo_note(lead_id, "Adyen: возврат платежа выполнен")
elif event_code == "CHARGEBACK":
create_kommo_note(lead_id, "Adyen: chargeback - требуется реакция")
create_kommo_task(lead_id, "Adyen: обработать chargeback")
return "[accepted]", 200
Важно: Adyen требует ответ [accepted] (строка) с кодом 200, иначе повторяет webhook до 10 раз.
Marketplace split payments
Если у вас marketplace-модель (вы продаёте от имени нескольких вендоров), Adyen поддерживает автоматическое разделение платежа через splits объект:
def create_split_payment_link(amount_cents: int, currency: str,
reference: str, splits: list) -> dict:
payload = {
"merchantAccount": ADYEN_MERCHANT,
"amount": {"currency": currency, "value": amount_cents},
"reference": reference,
"splits": splits,
}
resp = requests.post(
f"{ADYEN_BASE}/paymentLinks",
headers=ADYEN_HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
# splits пример: 90% вендору, 10% комиссия маркетплейса
SPLITS_EXAMPLE = [
{"type": "MarketPlace", "amount": {"value": 9000}, "account": "vendor_account_code"},
{"type": "Commission", "amount": {"value": 1000}},
]
Реальный кейс
B2B SaaS-маркетплейс (EU, 200+ enterprise клиентов, Kommo + Adyen):
- До: Won -> финансовый отдел вручную создавал payment request в Adyen Customer Area, отправлял ссылку по email. 1–2 рабочих дня задержки. Менеджер не видел статус оплаты без захода в Adyen.
- После: Won -> Python webhook -> Adyen Payment Link за 2 секунды -> ссылка в Note.
CAPTUREevent -> Note «оплачено» + смена этапа на «Paid». Менеджер видит весь цикл в Kommo без входа в Adyen. - Дополнительно:
CHARGEBACKevent -> Note + Task -> менеджер реагирует в день события. До интеграции chargeback-уведомления приходили только на email финансового директора.
Для кого актуально
- Enterprise B2B с Adyen как корпоративным стандартом (контракт требует конкретный gateway)
- Marketplace-компании где нужен автоматический split между продавцами и платформой
- Компании с multi-currency продажами в EU/US/APAC — Adyen поддерживает 150+ валют нативно
- Команды где менеджеры продаж должны видеть статус оплаты в CRM без доступа к Adyen Customer Area
Часто задаваемые вопросы
Adyen тестовый vs production — как переключать?
Adyen предоставляет тестовую среду (Customer Area Test) и production (Customer Area Live). URL отличаются: checkout-test.adyen.com vs checkout-live.adyen.com. API Keys разные для test и live. В коде: использовать переменную среды ADYEN_ENV = "test" | "live" для переключения base URL и API key.
Adyen Payment Links — срок действия и повторное использование?
Payment Link создаётся с expiresAt параметром (ISO 8601). После истечения — ссылка недействительна. Для повторного запроса платежа нужно создать новый Link через POST /paymentLinks. Статус существующего Link: GET /paymentLinks/{id} — поля status: active, expired, completed. При expired -> автоматически создавать новый через webhook или cron.
Как Adyen обрабатывает EU Strong Customer Authentication (SCA)?
Adyen нативно поддерживает 3DS2 для SCA. Payment Links включают 3DS challenge автоматически если банк клиента его требует. Дополнительной настройки не нужно — Adyen определяет необходимость challenge через AI-модель (exemptions: transaction risk analysis). Для B2B платежей с корпоративными картами SCA часто исключается через TRA-exemption.
Adyen webhook vs Adyen Reports — что использовать для сверки?
Webhooks: real-time события (AUTHORISATION, CAPTURE, REFUND). Adyen Reports: batch-файлы (CSV) доступные ежедневно через SFTP или Adyen Reporting API для бухгалтерской сверки. Для Kommo интеграции — webhooks. Для ERP/бухгалтерии — Reports API. Оба можно использовать параллельно.
Итого
- Auth: заголовок
X-API-Key, не Bearer token - Payment Link:
POST /checkout/v71/paymentLinks, reference = Kommo deal ID - Webhook: ответ
[accepted](строка), HMAC-SHA256 верификация обязательна - Ключевые события:
CAPTURE(деньги получены),CHARGEBACK(задача менеджеру),REFUND - Adyen split payments для marketplace:
splitsобъект в payload
Если вы используете Adyen и Kommo и хотите видеть статус платежей в карточке сделки — опишите вашу платёжную модель (одиночные платежи или marketplace). Exceltic.dev настроит двустороннюю интеграцию с HMAC-верификацией.