Razorpay - ведущий платёжный gateway Индии с охватом 100+ методов оплаты: UPI, NetBanking, карты, кошельки (Paytm, PhonePe), EMI. Работает в Индии, Малайзии, Сингапуре. Для B2B SaaS компаний, работающих с индийским рынком, интеграция Razorpay с Kommo решает стандартную задачу: создать Payment Link при достижении сделкой определённого этапа и автоматически перевести сделку в Closed Won при получении оплаты.
Razorpay API использует Basic Auth (Key ID + Key Secret). Payment Links API: POST /v1/payment_links - создать ссылку на оплату. Webhook: payment_link.paid и payment.captured - уведомление об оплате. Все суммы в Razorpay в пайсах (1 INR = 100 paise).
Razorpay Payment Link - страница оплаты с поддержкой всех индийских методов оплаты. Можно передать notes с произвольными метаданными - используем для хранения kommo_lead_id.
Архитектура
Kommo: сделка -> этап "Выставить счёт"
-> Kommo webhook: leads.status.changed
-> Ваш сервер
Ваш сервер
-> Razorpay API: POST /v1/payment_links
{amount_paise, description, notes.kommo_lead_id}
-> Kommo: записать ссылку как note
Клиент оплачивает через UPI/Card/NetBanking
-> Razorpay webhook: payment_link.paid
-> Ваш сервер: верифицировать подпись
-> Kommo: Closed Won + note с payment_id
Реализация: создание Payment Link
import requests, os, hmac, hashlib, json as json_mod
from flask import Flask, request, jsonify
app = Flask(__name__)
RZP_KEY_ID = os.environ["RAZORPAY_KEY_ID"]
RZP_KEY_SECRET = os.environ["RAZORPAY_KEY_SECRET"]
RZP_BASE = "https://api.razorpay.com/v1"
RZP_AUTH = (RZP_KEY_ID, RZP_KEY_SECRET)
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_STAGE = int(os.environ["KOMMO_CLOSED_WON_STAGE_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(lead_id: int) -> dict:
r = requests.get(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
params={"with": "contacts"},
)
return r.json()
def get_contact_details(contact_id: int) -> tuple[str, str, str]:
r = requests.get(
f"{KOMMO_BASE}/contacts/{contact_id}",
headers=KOMMO_HDR,
params={"with": "custom_fields_values"},
)
c = r.json()
email = ""
phone = ""
for cf in c.get("custom_fields_values", []) or []:
code = cf.get("field_code", "")
vals = cf.get("values", [])
if code == "EMAIL" and vals:
email = vals[0].get("value", "")
elif code == "PHONE" and vals:
phone = vals[0].get("value", "")
return c.get("name", ""), email, phone
def inr_to_paise(inr: float) -> int:
return int(inr * 100)
def create_payment_link(amount_inr: float, desc: str, lead_id: int,
contact_name: str, contact_email: str, contact_phone: str) -> str:
payload = {
"amount": inr_to_paise(amount_inr),
"currency": "INR",
"description": desc[:255],
"customer": {
"name": contact_name,
"email": contact_email,
"contact": contact_phone,
},
"notes": {
"kommo_lead_id": str(lead_id),
},
"reminder_enable": True,
"notify": {
"sms": bool(contact_phone),
"email": bool(contact_email),
},
}
r = requests.post(f"{RZP_BASE}/payment_links", auth=RZP_AUTH, json=payload)
r.raise_for_status()
return r.json().get("short_url", "")
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 = get_lead(lead_id)
budget = lead.get("price", 0) or 0
name = lead.get("name", f"Deal #{lead_id}")
contacts = lead.get("_embedded", {}).get("contacts", [])
cname = cemail = cphone = ""
if contacts:
cname, cemail, cphone = get_contact_details(contacts[0]["id"])
if budget <= 0:
add_note(lead_id, "Razorpay: сумма сделки не указана, создайте Payment Link вручную.")
continue
link = create_payment_link(float(budget), name, lead_id, cname, cemail, cphone)
add_note(lead_id, f"Razorpay Payment Link: {link}")
return jsonify({"status": "ok"}), 200
Реализация: webhook при оплате
def verify_razorpay_webhook(body: bytes, signature: str) -> bool:
# Razorpay HMAC-SHA256 с Key Secret
digest = hmac.new(RZP_KEY_SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(digest, signature)
@app.route("/webhooks/razorpay", methods=["POST"])
def razorpay_webhook():
sig = request.headers.get("X-Razorpay-Signature", "")
if not verify_razorpay_webhook(request.data, sig):
return jsonify({"error": "invalid signature"}), 401
event = request.json or {}
ev = event.get("event", "")
if ev not in ("payment_link.paid", "payment.captured"):
return jsonify({"status": "ignored"}), 200
if ev == "payment_link.paid":
pl = event.get("payload", {}).get("payment_link", {}).get("entity", {})
notes = pl.get("notes", {})
lead_id = notes.get("kommo_lead_id", "")
amount = pl.get("amount", 0) / 100 # paise -> INR
pay_id = event.get("payload", {}).get("payment", {}).get("entity", {}).get("id", "")
else:
pay_entity = event.get("payload", {}).get("payment", {}).get("entity", {})
notes = pay_entity.get("notes", {})
lead_id = notes.get("kommo_lead_id", "")
amount = pay_entity.get("amount", 0) / 100
pay_id = pay_entity.get("id", "")
if not lead_id:
return jsonify({"status": "no_lead_id"}), 200
requests.patch(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
json={"status_id": CLOSED_WON_STAGE},
)
add_note(
int(lead_id),
f"Razorpay: оплачено INR {amount:.2f}. Payment ID: {pay_id}",
)
return jsonify({"status": "ok"}), 200
Настройка Razorpay webhook
- Razorpay Dashboard -> Settings -> Webhooks -> Add New Webhook
- URL:
https://your-server.com/webhooks/razorpay - Events:
payment_link.paid,payment.captured - Secret: любая строка -> использовать как подпись
Razorpay подписывает каждый webhook с X-Razorpay-Signature - hex HMAC-SHA256 тела запроса с webhook secret (не Key Secret). Убедитесь что используете правильный секрет в verify_razorpay_webhook.
UPI и многообразие методов оплаты
Razorpay Payment Link автоматически показывает все доступные методы оплаты:
- UPI (GPay, PhonePe, Paytm, BHIM) - 70%+ транзакций в Индии
- NetBanking - все крупные банки
- Карты (Visa, Mastercard, RuPay)
- Кошельки (Paytm, Amazon Pay)
- EMI (без карты, через банки)
Никакой дополнительной настройки - всё доступно по умолчанию.
Для международных платежей
Razorpay поддерживает 100+ валют через Razorpay International (отдельное подключение). Для INR-платежей достаточно стандартного аккаунта. Для Malaysia/Singapore - Razorpay Curlec (отдельный продукт).
Реальный кейс
B2B SaaS, ориентированный на Индию, 40 сделок в месяц, средний чек 25 000 INR. До интеграции: менеджеры создавали Payment Links вручную в Razorpay Dashboard. После: ссылка создаётся при переводе сделки в этап “Оплата”. Razorpay автоматически отправляет WhatsApp-напоминание клиенту через reminder_enable.
Для кого актуально
B2B SaaS и сервисные компании с клиентами в Индии. Особенно если UPI - основной метод оплаты клиентов. Разработчики из Индии часто строят продукты на Razorpay как первом платёжном gateway перед выходом на глобальный рынок.
Аналогичная интеграция для европейского рынка описана для Kommo + Mollie и Kommo + GoCardless.
Часто задаваемые вопросы
Как Razorpay обрабатывает GST для B2B в Индии?
Razorpay поддерживает GST в инвойсах. При создании Payment Link можно добавить line_items с tax_amount. Для B2B сделок с GST нужен отдельный налоговый инвойс - Razorpay генерирует его автоматически при правильной настройке GST регистрации в Dashboard.
Есть ли лимиты на суммы в Razorpay Payment Links?
Максимальная сумма одного Payment Link: 500 000 INR (~$6 000). Для больших сделок создавайте несколько ссылок или используйте Razorpay Invoice API с разбивкой на части. Для enterprise-сделок (>10 lakhs INR) лучше использовать Razorpay NACH (прямое дебетование).
Как работает возврат через Razorpay API?
POST /v1/payments/{payment_id}/refund с {amount: paise}. Частичный или полный возврат. После возврата обновите статус сделки в Kommo через Kommo API - Razorpay не уведомляет об успешном возврате через тот же webhook, нужно слушать отдельное событие refund.processed.
Итог
Kommo + Razorpay - платёжный gateway для India:
- Basic Auth (Key ID + Key Secret), суммы в пайсах (INR x 100)
notes.kommo_lead_idдля корреляции webhook -> сделка- Webhook
payment_link.paid-> HMAC-SHA256 верификация -> Closed Won reminder_enable: true- Razorpay автоматически напоминает клиенту- UPI + NetBanking + карты + кошельки из коробки без дополнительной настройки
Если ваша команда работает с индийским рынком через Razorpay и Kommo - опишите задачу команде Exceltic.dev.