Flutterwave - ведущий платёжный gateway Африки: работает в 34+ странах, поддерживает mobile money (M-Pesa, MTN MoMo, Airtel Money), банковские переводы, карты и кошельки. Для B2B компаний, выходящих на африканские рынки с Kommo в качестве CRM, интеграция решает стандартную задачу: создать Payment Link при достижении определённого этапа воронки, получить webhook при успешной оплате и автоматически закрыть сделку.
Flutterwave API использует Bearer token. Payment Links API: POST /v3/payment-links - создать ссылку на оплату. Webhook: charge.completed - уведомление об успешном платеже. Ключевой параметр - tx_ref (transaction reference): произвольная строка, которую Flutterwave возвращает в webhook - используем для хранения kommo_lead_id.
Flutterwave Payment Link - размещённая страница оплаты с поддержкой всех местных методов: M-Pesa для Кении, MTN MoMo для Ганы/Уганды, Airtel Money для Замбии, карты Visa/Mastercard для всей Африки. Клиент выбирает удобный способ, вы получаете webhook при оплате.
Методы оплаты по странам
| Страна | Основные методы |
|---|---|
| Нигерия | Банковский перевод, карты, USSD |
| Кения | M-Pesa, карты, банковский перевод |
| Гана | MTN MoMo, Vodafone Cash, карты |
| Уганда | MTN MoMo, Airtel Money |
| Южная Африка | Карты, EFT (банковский перевод) |
| Египет | Карты, Fawry, ValU |
Все методы доступны через один Payment Link - Flutterwave сам определяет доступные способы по IP клиента.
Архитектура
Kommo: сделка -> этап "Выставить счёт"
-> Kommo webhook leads.status.changed
-> Ваш сервер
Ваш сервер
-> Kommo API: получить email, имя, сумму
-> Flutterwave: POST /v3/payment-links
{amount, currency, tx_ref: "kommo_{lead_id}", customer}
-> Kommo: добавить note со ссылкой
Клиент оплачивает через M-Pesa / карту / Mobile Money
-> Flutterwave webhook: charge.completed
-> Ваш сервер: верифицировать подпись -> Kommo Closed Won
Реализация: создание Payment Link
import requests, os, hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
FW_SECRET_KEY = os.environ["FLUTTERWAVE_SECRET_KEY"]
FW_BASE = "https://api.flutterwave.com/v3"
FW_HDR = {"Authorization": f"Bearer {FW_SECRET_KEY}", "Content-Type": "application/json"}
FW_WEBHOOK_HASH = os.environ["FLUTTERWAVE_WEBHOOK_HASH"]
KOMMO_SUBDOMAIN = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
INVOICE_STAGE_ID = int(os.environ["KOMMO_INVOICE_STAGE_ID"])
CLOSED_WON_ID = int(os.environ["KOMMO_CLOSED_WON_ID"])
KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR = {"Authorization": f"Bearer {KOMMO_TOKEN}", "Content-Type": "application/json"}
def get_lead_contact(lead_id: int) -> tuple[dict, dict]:
r = requests.get(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
params={"with": "contacts"},
)
lead = r.json()
contacts = lead.get("_embedded", {}).get("contacts", [])
contact = {}
if contacts:
rc = requests.get(
f"{KOMMO_BASE}/contacts/{contacts[0]['id']}",
headers=KOMMO_HDR,
params={"with": "custom_fields_values"},
)
contact = rc.json()
return lead, contact
def get_email(contact: dict) -> str:
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
vals = cf.get("values", [])
if vals:
return vals[0].get("value", "")
return ""
def get_phone(contact: dict) -> str:
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "PHONE":
vals = cf.get("values", [])
if vals:
return vals[0].get("value", "")
return ""
def create_payment_link(amount: float, currency: str, lead_id: int,
name: str, email: str, phone: str) -> str:
r = requests.post(
f"{FW_BASE}/payment-links",
headers=FW_HDR,
json={
"name": f"Invoice #{lead_id}",
"amount": amount,
"currency": currency, # NGN, KES, GHS, USD и др.
"redirect_url": os.environ.get("PAYMENT_SUCCESS_URL", ""),
"meta": [
{"metaname": "kommo_lead_id", "metavalue": str(lead_id)},
],
"is_permanent": False,
"customer": {
"name": name,
"email": email,
"phonenumber": phone,
},
},
)
r.raise_for_status()
return r.json().get("data", {}).get("link", "")
def add_note(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, contact = get_lead_contact(lead_id)
amount = float(lead.get("price") or 0)
currency = os.environ.get("DEAL_CURRENCY", "USD")
email = get_email(contact)
phone = get_phone(contact)
name = contact.get("name", "")
if amount <= 0 or not email:
add_note(lead_id, "Flutterwave: сумма или email не указаны, создайте ссылку вручную.")
continue
link = create_payment_link(amount, currency, lead_id, name, email, phone)
add_note(lead_id, f"Flutterwave Payment Link: {link}")
return jsonify({"status": "ok"}), 200
Реализация: webhook при оплате
def verify_flutterwave_webhook(secret_hash: str, received_hash: str) -> bool:
# Flutterwave использует статичный Secret Hash (не HMAC)
# Задаётся в Dashboard -> Settings -> Webhooks -> Secret Hash
return hmac.compare_digest(secret_hash, received_hash)
@app.route("/webhooks/flutterwave", methods=["POST"])
def flutterwave_webhook():
received = request.headers.get("verif-hash", "")
if not verify_flutterwave_webhook(FW_WEBHOOK_HASH, received):
return jsonify({"error": "invalid hash"}), 401
event = request.json or {}
if event.get("event") != "charge.completed":
return jsonify({"status": "ignored"}), 200
data = event.get("data", {})
status = data.get("status", "")
if status != "successful":
return jsonify({"status": "not_successful"}), 200
# Извлечь kommo_lead_id из meta
meta = data.get("meta", {}) or {}
lead_id = meta.get("kommo_lead_id", "")
if not lead_id:
# Попробовать tx_ref (fallback)
tx_ref = data.get("tx_ref", "")
if tx_ref.startswith("kommo_"):
lead_id = tx_ref.replace("kommo_", "")
if not lead_id:
return jsonify({"status": "no_lead_id"}), 200
amount = data.get("amount", 0)
currency = data.get("currency", "")
flw_ref = data.get("flw_ref", "")
payment_type = data.get("payment_type", "")
requests.patch(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
json={"status_id": CLOSED_WON_ID},
)
add_note(
int(lead_id),
f"Flutterwave: оплачено {amount} {currency} через {payment_type}. Ref: {flw_ref}",
)
return jsonify({"status": "ok"}), 200
Настройка Flutterwave webhook
Dashboard -> Settings -> Webhooks. URL: https://your-server.com/webhooks/flutterwave. Secret Hash: произвольная строка (используется в заголовке verif-hash, не HMAC). Активируйте событие charge.completed.
Flutterwave НЕ использует HMAC-SHA256 для webhook - вместо этого отправляет статичный Secret Hash в заголовке. Это менее безопасно чем HMAC, но достаточно если hash сложный и хранится в секрете.
Валюты и конвертация
Flutterwave поддерживает 35+ валют. Для B2B сделок в USD с оплатой в местных валютах используйте currency: "USD" в Payment Link - Flutterwave автоматически конвертирует по рыночному курсу. Сумма в webhook будет в исходной валюте платежа.
Верификация транзакции через API
После получения webhook рекомендуется верифицировать транзакцию напрямую:
def verify_transaction(transaction_id: int) -> bool:
r = requests.get(
f"{FW_BASE}/transactions/{transaction_id}/verify",
headers=FW_HDR,
)
data = r.json().get("data", {})
return data.get("status") == "successful"
flw_ref из webhook содержит id транзакции - передайте его в verify endpoint для подтверждения.
Для кого актуально
B2B SaaS и сервисные компании с клиентами в Нигерии, Кении, Гане, Уганде, ЮАР. Особенно актуально для EdTech, SaaS и профессиональных услуг, где клиент платит разово или по подписке. Flutterwave также работает в Европе и США через Stripe-подобный API для компаний с европейской регистрацией.
Аналогичные интеграции для других рынков: Kommo + Razorpay (India), Kommo + Mollie (Европа).
Часто задаваемые вопросы
Как Flutterwave обрабатывает VAT и налоги для B2B в Нигерии?
Flutterwave не генерирует автоматически налоговые инвойсы. Для Нигерии VAT 7.5% нужно включать в сумму Payment Link (amount = стоимость услуги + VAT). Официальный FIRS-compliant инвойс оформляйте отдельно через бухгалтерский сервис (например, Wave или Zoho Books с нигерийским VAT модулем).
Как получить выплату: в какой валюте и на какой счёт?
Flutterwave поддерживает выплаты на банковские счета в Нигерии, Кении, ЮАР, Великобритании, США. Для неаффилированных стран - через Flutterwave Send Money или партнёрские банки. Конвертация происходит по курсу Flutterwave при создании выплаты. Минимальная сумма выплаты: $100 эквивалент.
Работает ли Flutterwave для recurring payments / подписок?
Да, через Flutterwave Subscriptions API: POST /v3/payment-plans - создать тарифный план, затем привязать клиента. Webhook subscription.charge при каждом списании. Для простых одноразовых платежей Payment Links достаточно - подписки нужны только для recurring billing.
Итог
Kommo + Flutterwave - платёжный gateway для Африки и EM:
- Bearer token,
POST /v3/payment-linksсmeta.kommo_lead_id - Верификация: статичный Secret Hash в заголовке
verif-hash(не HMAC) charge.completedwebhook -> verify transaction -> Kommo Closed Won- 35+ валют, mobile money (M-Pesa, MTN MoMo) из коробки
- Verify transaction через
/v3/transactions/{id}/verifyперед закрытием сделки
Если ваша команда работает с африканскими рынками через Kommo и Flutterwave - опишите задачу команде Exceltic.dev.