Как сохранить дату оплаты в поле сделки
Scope:
crmКто может выполнять методы: чтобы пройти сценарий целиком, нужно самое строгое из перечисленных прав — «изменения» элементов объекта CRM
- crm.item.update — пользователь с правом «изменения» элементов объекта CRM
- crm.item.fields и crm.item.get — пользователь с правом «чтения» элементов объекта CRM
- crm.item.payment.list — пользователь с правом на чтение объекта CRM, из которого выбираются оплаты
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
Дату оплаты Битрикс24 хранит в документе оплаты, а не в самой сделке. В карточке сделки этой даты нет, и обычный фильтр по сделкам ее не видит. Поэтому дату оплаты часто дублируют в пользовательское поле сделки: оттуда ее забирают интеграции с внешними системами, отчеты BI-конструктора, роботы и бизнес-процессы.
Идентификатор пользовательского поля в каждом Битрикс24 свой, и записать его в код константой нельзя. Поэтому поле придется каждый раз находить по названию.
В результате сценария в поле «Дата оплаты» карточки сделки появится дата оплаты, а crm.item.update вернет сделку с новым значением поля.
Сценарий состоит из трех шагов.
- Найти идентификатор поля сделки методом crm.item.fields
- Получить дату оплаты методом crm.item.payment.list
- Записать дату в поле сделки методом crm.item.update
Что нужно до начала
-
вебхук создан от имени пользователя, у которого есть право изменять сделки в CRM
-
в правах вебхука отмечен scope
crm -
путь вебхука дает полный доступ в рамках своего scope. Храните путь в переменной окружения и не публикуйте его в открытом коде
-
в карточке сделки заранее создано пользовательское поле для даты оплаты. Его добавляют в настройках карточки сделки или методом crm.deal.userfield.add. Как выбрать тип поля, описано в блоке Что важно учитывать
-
известен
idсделки, для которой переносится дата. Найти его можно в адресе карточки сделки или методом crm.item.list -
по этой сделке проведена хотя бы одна оплата. Если оплат нет, шаг 2 вернет пустой массив, и записывать в сделку будет нечего
Дальше в примерах используется сделка 6917 и поле с названием «Дата оплаты».
1. Найдем идентификатор поля сделки
Используем метод crm.item.fields с параметром:
entityTypeId— идентификатор типа объекта CRM, обязательный параметр. Укажем2— сделка
Метод возвращает объект fields: ключ — идентификатор поля, значение — его настройки. Нужное поле найдем перебором по паре признаков:
-
title— название поля, которое видит пользователь в карточке. Ищем «Дата оплаты» -
type— тип поля. Проверяем, что этоdateилиdatetime: так название вроде «Дата оплаты» у строкового поля не собьет отбор
Как использовать примеры в документации
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/'
const resultFields = await $b24.actions.v2.call.make({
method: 'crm.item.fields',
params: {
entityTypeId: 2 // 2 — сделка
},
requestId: 'item-fields'
});
const fields = resultFields.getData().result.fields;
const fieldName = Object.keys(fields).find(
key => fields[key].title === 'Дата оплаты'
&& ['date', 'datetime'].includes(fields[key].type)
);
# pip install b24pysdk
from b24pysdk import BitrixWebhook, Client
client = Client(
BitrixWebhook(
domain="your-domain.bitrix24.ru",
webhook_token="USER_ID/TOKEN", # только user_id/token, без https://
)
)
fields = client.crm.item.fields(
2, # 2 — сделка
).response.result["fields"]
field_name = next(
(
key
for key, settings in fields.items()
if settings["title"] == "Дата оплаты" and settings["type"] in ("date", "datetime")
),
None,
)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Psr\Log\NullLogger;
$sb = (new ServiceBuilderFactory(new EventDispatcher(), new NullLogger()))
->initFromWebhook('https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/');
// у crm.item.fields нет обертки в SDK — вызываем метод напрямую
$resultFields = $sb->core->call(
'crm.item.fields',
[ 'entityTypeId' => 2 ] // 2 — сделка
);
$fields = $resultFields->getResponseData()->getResult()['fields'];
$fieldName = null;
foreach ($fields as $key => $settings) {
if ($settings['title'] === 'Дата оплаты' && in_array($settings['type'], ['date', 'datetime'], true)) {
$fieldName = $key;
break;
}
}
Сохраните найденный идентификатор — он нужен на шаге 3. В примере это ufCrm_1746431727372. Ответ сокращен до одного поля — метод возвращает весь состав полей сделки.
{
"result": {
"fields": {
"ufCrm_1746431727372": {
"type": "date",
"isRequired": false,
"isReadOnly": false,
"isImmutable": false,
"isMultiple": false,
"isDynamic": true,
"title": "Дата оплаты",
"listLabel": "Дата оплаты",
"formLabel": "Дата оплаты",
"filterLabel": "Дата оплаты",
"settings": {
"DEFAULT_VALUE": {
"TYPE": "NONE",
"VALUE": ""
}
},
"upperName": "UF_CRM_1746431727372"
}
}
}
}
Признак isDynamic: true подтверждает, что поле пользовательское, а не системное. В upperName лежит то же поле в старом написании — UF_CRM_1746431727372. Метод crm.item.update по умолчанию понимает только вариант из ключа, поэтому на шаге 3 передаем именно ufCrm_1746431727372.
2. Получим дату оплаты
Используем метод crm.item.payment.list с параметрами:
-
entityTypeId— идентификатор типа объекта CRM, обязательный параметр. Укажем2— сделка -
entityId— идентификатор сделки, для которой получаем оплаты, обязательный параметр. В примере6917
const resultPayments = await $b24.actions.v2.call.make({
method: 'crm.item.payment.list',
params: {
entityTypeId: 2,
entityId: 6917
},
requestId: 'payment-list'
});
const payments = resultPayments.getData().result;
payments = client.crm.item.payment.list(
entity_type_id=2,
entity_id=6917,
).response.result
// у crm.item.payment.list нет обертки в SDK — вызываем метод напрямую
$resultPayments = $sb->core->call(
'crm.item.payment.list',
[
'entityTypeId' => 2,
'entityId' => 6917
]
);
$payments = $resultPayments->getResponseData()->getResult();
Метод возвращает массив оплат сделки. Дату платежа возьмите из поля datePaid, а по полю paid проверьте, что оплата действительно проведена: у неоплаченного документа paid равно N, а datePaid пустое.
{
"result": [
{
"id": 503,
"accountNumber": "831/1",
"paid": "Y",
"datePaid": "2025-04-29T13:03:20+03:00",
"empPaidId": 1,
"paySystemId": 19,
"sum": 15,
"currency": "RUB",
"paySystemName": "ЮKassa"
}
]
}
3. Запишем дату в поле сделки
Используем метод crm.item.update с параметрами:
-
entityTypeId—2для сделки -
id— идентификатор сделки, в примере6917 -
fields[ufCrm_1746431727372]— идентификатор поля из шага 1. Значением передаемdatePaidиз шага 2
const resultUpdate = await $b24.actions.v2.call.make({
method: 'crm.item.update',
params: {
entityTypeId: 2,
id: 6917,
fields: {
// ключ — идентификатор поля из шага 1, значение — datePaid из шага 2
[fieldName]: payments[0].datePaid
}
},
requestId: 'item-update'
});
result_update = client.crm.item.update(
2,
6917,
{
# ключ — идентификатор поля из шага 1, значение — datePaid из шага 2
field_name: payments[0]["datePaid"],
},
).response.result["item"]
$resultUpdate = $sb->getCRMScope()->item()->update(
2,
6917,
[
// ключ — идентификатор поля из шага 1, значение — datePaid из шага 2
$fieldName => $payments[0]['datePaid']
]
);
Метод возвращает сделку целиком уже с новым значением поля, поэтому проверять запись отдельным запросом не обязательно. Ответ сокращен до полей, которые подтверждают запись.
{
"result": {
"item": {
"id": 6917,
"title": "Сделка #6531",
"stageId": "C9:NEW",
"opportunity": 30,
"currencyId": "RUB",
"updatedTime": "2026-08-20T09:14:13+03:00",
"ufCrm_1746431727372": "2025-04-29T03:00:00+03:00"
}
}
}
Записали 2025-04-29T13:03:20+03:00, а в ответе пришло 2025-04-29T03:00:00+03:00. Это не ошибка: поле имеет тип «Дата», поэтому время не сохраняется. Время в ответе служебное и не зависит от того, что вы отправили: значения 2025-04-29, 2025-04-29T00:00:00+03:00 и 2025-04-29T23:59:00+03:00 дадут один и тот же ответ. Сверяйте с отправленным значением только дату. Если время оплаты важно, заведите поле типа «Дата/время» — оно сохраняет значение целиком.
Проверим результат
Откройте карточку сделки в CRM. В поле «Дата оплаты» стоит 29.04.2025 — та же дата, что и в документе оплаты.
Через REST значение поля возвращает метод crm.item.get с параметрами:
-
entityTypeId—2для сделки -
id— идентификатор сделки, в примере6917
const checkResult = await $b24.actions.v2.call.make({
method: 'crm.item.get',
params: { entityTypeId: 2, id: 6917 },
requestId: 'item-check'
});
console.log(checkResult.getData().result.item[fieldName]);
print(client.crm.item.get(2, 6917).response.result["item"][field_name])
echo $sb->getCRMScope()->item()->get(2, 6917)->item()->{$fieldName};
Сценарий выполнен, если в ответе у поля ufCrm_1746431727372 стоит дата оплаты, а не null и не пустая строка.
{
"result": {
"item": {
"id": 6917,
"title": "Сделка #6531",
"ufCrm_1746431727372": "2025-04-29T03:00:00+03:00"
}
}
}
Ошибки и диагностика
Если метод вернул ошибку, проверьте данные запроса.
|
Код |
Причина и действие |
|
|
Элемент не найден. Проверьте |
|
|
В |
|
|
У пользователя вебхука нет права изменять сделки. Проверьте, от чьего имени создан вебхук |
|
|
Вебхук создан от имени внешнего пользователя. Сценарий доступен только сотрудникам Битрикс24 |
Метод crm.item.update возвращает ошибку редко. Неизвестный идентификатор поля, недопустимое значение даты и лишние поля он отбрасывает и отвечает успехом.
Отдельно проверьте случаи, когда ответ успешный, а результат отличается от ожидаемого.
-
шаг 1 не нашел поле,
fieldNameпустой — в Битрикс24 нет поля с таким названием либо у него другой тип. Сверьте название с карточкой сделки: отбор идет по точному совпадениюtitle, поэтому лишний пробел или другой регистр его сломают -
шаг 2 вернул пустой массив — по сделке нет оплат либо в
entityTypeIdпередан не тот тип объекта. При неверномentityTypeIdметод не отказывает, а возвращает пустой результат -
шаг 3 отработал, но поле осталось пустым — идентификатор передан в старом написании
UF_CRM_1746431727372. Передавайте идентификатор из шага 1 или добавьте в запросuseOriginalUfNames:Y -
в поле оказалась не та дата — в поле типа «Дата» время отбрасывается, подробности в блоке Что важно учитывать
Шаги 1 и 2 ничего не меняют, их можно повторять сколько угодно раз. Если ошибку вернул шаг 3, сверьте текущее значение поля по разделу «Проверим результат», исправьте запрос и повторите только шаг 3.
Что важно учитывать
-
тип поля решает, сохранится ли время оплаты: «Дата» оставляет только дату, «Дата/время» — значение целиком. Выбирайте тип до того, как запустите перенос по всей базе: у заполненных полей время уже не восстановить
-
параметр
useOriginalUfNames:Yменяет и принимаемые, и возвращаемые идентификаторы: с ним ответ приходит с ключомUF_CRM_1746431727372. Читайте значение по тому идентификатору, который задает параметр, а не по тому, что отправили -
значение даты метод принимает в двух форматах:
2025-04-29T13:03:20+03:00и29.04.2025 -
у сделки может быть несколько оплат, и метод возвращает их все. Фрагменты шагов для краткости берут первую запись массива, а она не обязательно последняя по времени и не обязательно проведенная
-
правильный отбор показан в блоке Пример кода: записи с
paid:Y, из них максимальныйdatePaid. Если нужна первая оплата или сумма по всем, измените условие отбора -
поле сделки — это копия даты, а не связь с документом оплаты. Если оплату отменят или проведут заново, значение в сделке не обновится само. Перезапускайте сценарий по расписанию или каждый раз, когда меняете оплаты сделки
-
тот же сценарий работает для других типов объектов CRM, у которых есть оплаты: поменяйте
entityTypeIdво всех трех шагах. Идентификаторы типов приведены в справочнике типов объектов CRM
Пример кода
Скрипт находит пользовательское поле сделки по названию, читает дату проведенной оплаты и записывает ее в это поле. Название поля и id сделки вынесены в переменные в начале скрипта.
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/'
const ENTITY_TYPE_ID = 2; // 2 — сделка
const DEAL_ID = 6917; // укажите свою сделку
const FIELD_TITLE = 'Дата оплаты'; // название поля в карточке сделки
async function call(method, params, requestId) {
const result = await $b24.actions.v2.call.make({ method, params, requestId });
if (!result.isSuccess) {
throw new Error(result.getErrorMessages().join('; '));
}
return result.getData().result;
}
async function setPaidDate() {
try {
// Шаг 1: находим идентификатор поля по его названию и типу
const { fields } = await call('crm.item.fields', {
entityTypeId: ENTITY_TYPE_ID
}, 'item-fields');
const fieldName = Object.keys(fields).find(
key => fields[key].title === FIELD_TITLE
&& ['date', 'datetime'].includes(fields[key].type)
);
if (!fieldName) {
console.error(`Поле «${FIELD_TITLE}» с типом «Дата» не найдено в карточке сделки`);
return;
}
console.log('Идентификатор поля:', fieldName);
// Шаг 2: читаем дату проведенной оплаты
const payments = await call('crm.item.payment.list', {
entityTypeId: ENTITY_TYPE_ID,
entityId: DEAL_ID
}, 'payment-list');
const paid = payments.filter(payment => payment.paid === 'Y' && payment.datePaid);
if (paid.length === 0) {
console.error(`По сделке ${DEAL_ID} нет проведенных оплат`);
return;
}
// берем последнюю по времени оплату, а не первую из массива
const datePaid = paid.map(payment => payment.datePaid).sort().pop();
console.log('Дата оплаты:', datePaid);
// Шаг 3: записываем дату в поле сделки
const updated = await call('crm.item.update', {
entityTypeId: ENTITY_TYPE_ID,
id: DEAL_ID,
fields: { [fieldName]: datePaid }
}, 'item-update');
// поле типа «Дата» отбрасывает время, поэтому сверяем значение в ответе
console.log('Записано в сделку:', updated.item[fieldName]);
} catch (error) {
console.error('Дата оплаты не записана:', error.message);
}
}
setPaidDate();
# pip install b24pysdk
from b24pysdk import BitrixWebhook, Client
from b24pysdk.errors import BitrixAPIError
client = Client(
BitrixWebhook(
domain="your-domain.bitrix24.ru",
webhook_token="USER_ID/TOKEN", # только user_id/token, без https://
)
)
ENTITY_TYPE_ID = 2 # 2 — сделка
DEAL_ID = 6917 # укажите свою сделку
FIELD_TITLE = "Дата оплаты" # название поля в карточке сделки
try:
# Шаг 1: находим идентификатор поля по его названию и типу
fields = client.crm.item.fields(
ENTITY_TYPE_ID,
).response.result["fields"]
field_name = next(
(
key
for key, settings in fields.items()
if settings["title"] == FIELD_TITLE and settings["type"] in ("date", "datetime")
),
None,
)
if field_name is None:
print(f"Поле «{FIELD_TITLE}» с типом «Дата» не найдено в карточке сделки")
else:
print(f"Идентификатор поля: {field_name}")
# Шаг 2: читаем дату проведенной оплаты
payments = client.crm.item.payment.list(
entity_type_id=ENTITY_TYPE_ID,
entity_id=DEAL_ID,
).response.result
dates = [
payment["datePaid"]
for payment in payments
if payment["paid"] == "Y" and payment["datePaid"]
]
if not dates:
print(f"По сделке {DEAL_ID} нет проведенных оплат")
else:
# берем последнюю по времени оплату, а не первую из массива
date_paid = max(dates)
print(f"Дата оплаты: {date_paid}")
# Шаг 3: записываем дату в поле сделки
updated = client.crm.item.update(
ENTITY_TYPE_ID,
DEAL_ID,
{field_name: date_paid},
).response.result["item"]
# поле типа «Дата» отбрасывает время, поэтому сверяем значение в ответе
print(f"Записано в сделку: {updated[field_name]}")
except BitrixAPIError as error:
print(f"Дата оплаты не записана: {error}")
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Psr\Log\NullLogger;
$sb = (new ServiceBuilderFactory(new EventDispatcher(), new NullLogger()))
->initFromWebhook('https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/');
$entityTypeId = 2; // 2 — сделка
$dealId = 6917; // укажите свою сделку
$fieldTitle = 'Дата оплаты'; // название поля в карточке сделки
try {
// Шаг 1: находим идентификатор поля по его названию и типу
// у crm.item.fields нет обертки в SDK — вызываем метод напрямую
$resultFields = $sb->core->call(
'crm.item.fields',
[ 'entityTypeId' => $entityTypeId ]
);
$fields = $resultFields->getResponseData()->getResult()['fields'];
$fieldName = null;
foreach ($fields as $key => $settings) {
if ($settings['title'] === $fieldTitle && in_array($settings['type'], ['date', 'datetime'], true)) {
$fieldName = $key;
break;
}
}
if ($fieldName === null) {
echo 'Поле «' . $fieldTitle . '» с типом «Дата» не найдено в карточке сделки';
return;
}
echo 'Идентификатор поля: ' . $fieldName . PHP_EOL;
// Шаг 2: читаем дату проведенной оплаты
// у crm.item.payment.list нет обертки в SDK — вызываем метод напрямую
$resultPayments = $sb->core->call(
'crm.item.payment.list',
[
'entityTypeId' => $entityTypeId,
'entityId' => $dealId
]
);
$payments = $resultPayments->getResponseData()->getResult();
$dates = [];
foreach ($payments as $payment) {
if ($payment['paid'] === 'Y' && !empty($payment['datePaid'])) {
$dates[] = $payment['datePaid'];
}
}
if ($dates === []) {
echo 'По сделке ' . $dealId . ' нет проведенных оплат';
return;
}
// берем последнюю по времени оплату, а не первую из массива
sort($dates);
$datePaid = end($dates);
echo 'Дата оплаты: ' . $datePaid . PHP_EOL;
// Шаг 3: записываем дату в поле сделки
$updated = $sb->getCRMScope()->item()->update(
$entityTypeId,
$dealId,
[ $fieldName => $datePaid ]
);
// поле типа «Дата» отбрасывает время, поэтому сверяем значение в ответе
echo 'Записано в сделку: ' . $updated->item()->{$fieldName};
} catch (\Throwable $e) {
echo 'Дата оплаты не записана: ' . $e->getMessage();
}