Есть ошибки, которые невозможно предотвратить внимательностью.

Клиент присылает фотографию заявления на возврат. На ней от руки написан расчётный счёт — двадцать цифр. Сотрудник переносит их в платёжное поручение. В какой-то момент один человек из ста ошибётся: перепутает местами две цифры, примет тройку за восьмёрку, съедет строкой. Платёж уйдёт, банк его развернёт, деньги вернутся, клиент прождёт ещё неделю и напишет злое сообщение.

Виноватых нет. Все старались. Просто задача «безошибочно переписать двадцать цифр с фото» не решается старанием — она решается тем, что её не должно существовать.

Ниже — как мы перестроили самый ручной процесс в компании, и главное: почему проверить банковские реквизиты можно математически, ничего не запрашивая у банка. С кодом, который можно скопировать и использовать.

 Возврат денег до и после перестройки. Один квадрат — одно действие сотрудника или системы.
Возврат денег до и после перестройки. Один квадрат — одно действие сотрудника или системы.

Процесс, у которого не было хозяина

Возврат денег выглядел так. Клиент от руки заполнял заявление и печатал его. Менеджер вручную переносил данные в систему поддержки, оформлял забор товара в CRM, ставил задачу складу, заводил бизнес-процесс. Бухгалтерия перепечатывала реквизиты с фотографии.

Двадцать шесть шагов. Шесть участников. Пять систем, между собой не связанных.

Но интереснее другое. Когда я полез разбираться, выяснилось, что задачи на этот процесс не существовало в природе. Были обращения от отдела заботы — про то, что клиенты жалуются на сроки. От бухгалтерии — про кривые реквизиты. От склада — про то, что непонятно, что приедет и когда. Каждый отдел видел свой фрагмент и считал его отдельной проблемой.

Это типичная причина, по которой сквозные процессы годами не чинят. Не потому, что сложно. А потому, что нет человека, у которого болит целиком, — а значит, некому и поставить задачу.

Первое, что я сделал, — не написал ни строчки кода. Собрал все эти обращения в одно и пошёл разговаривать.

Три разговора, которые определили архитектуру

К юристу, в бухгалтерию и в отдел заботы я пришёл с разными вопросами, но по сути с одним: что здесь обязательно, а что мы делаем по привычке?

Привычек оказалось сильно больше, чем требований.

Заявление убрать нельзя — но не по той причине, по которой все думали. У него две настоящие функции. Первая: это волеизъявление, где клиент явно заявляет, что отказывается от товара и просит вернуть деньги. Вторая: это документ-основание для бухгалтерии, без которого деньги не могут покинуть счёт компании. Всё остальное — печать, ручная подпись, скан — оказалось не законом, а традицией.

Юрист подтвердил: волеизъявление подтверждается простой электронной подписью — либо подписью прямо в форме, либо кодом из СМС. Оба варианта законны. Выбрали первое: СМС дороже и добавляет клиенту лишний шаг.

Бухгалтерия сказала ещё интереснее: формат заявления может быть любым. Важно, чтобы документ существовал и данные в нём совпадали с платёжкой.

После этих двух ответов стало понятно, что строить.

Ключевая идея: данные вводит тот, у кого они есть

Раньше данные вводил тот, кто их не знает: менеджер и бухгалтер переписывали за клиентом. Теперь их вводит сам клиент, а проверку берёт на себя машина.

Менеджер генерирует ссылку прямо из тикета в системе поддержки. Ссылка привязана к обращению, поэтому заявка автоматически сцепляется с перепиской и не бывает форм, прилетевших непонятно от кого. Общий адрес формы закрыт заглушкой: заполнить её «мимо тикета» нельзя.

Дальше клиент проходит шесть коротких шагов: данные покупки, данные покупателя, причина возврата, способ передачи товара, реквизиты, чек и подпись.

Несколько решений, которые выглядят мелочью, но заметно влияют на то, дойдёт человек до конца или бросит:

  • ФИО обязательно с отчеством. Не бюрократия формы: без отчества банк не проведёт платёж. В подсказке так и написано, чтобы человек не думал, что мы придираемся.

  • Пункт выдачи выбирается на карте, а курьер предлагается только там, где он реально ездит. Выпадающий список из трёхсот адресов — это способ заставить клиента ошибиться.

  • Черновик сохраняется сам. Закрыл вкладку, сел телефон — открыл ту же ссылку, и всё на месте, кроме файлов: их надо приложить заново.

  • Повторная отправка не блокируется. Если по ссылке заявку уже отправляли, клиент видит сообщение об этом, но форма остаётся рабочей: возврат может быть на второй товар.

Теперь к главному.

Почему клиент физически не может ошибиться в реквизитах

Каждый раз, когда в компании заходил разговор про онлайн-форму, её хоронил один аргумент: клиент введёт что попало, и мы получим ошибку в платеже. Аргумент честный. Ошибка сотрудника хотя бы отлавливается на сверке, а тут данные идут напрямую в документ.

У этого аргумента есть слабое место. Он предполагает, что единственный способ проверить реквизиты — глазами. А это не так.

Российские банковские реквизиты содержат встроенную контрольную сумму. Она существует именно для того, чтобы ловить опечатки, и её достаточно посчитать локально — без обращения к банку, без интеграций и без задержек.

Контрольный ключ расчётного счёта

Счёт проверяется вместе с БИК: из них собирается строка из 23 цифр, каждая умножается на свой вес, сумма должна делиться на десять без остатка. Веса заданы методикой ЦБ и повторяются циклом 7, 1, 3.

Тонкость, из-за которой чаще всего ломаются самописные валидаторы, — какой префикс приписывать к счёту. Если последние три цифры БИК больше единицы, берутся они. Если это 000 или 001 — берётся ноль и пятый-шестой символы БИК: такие счета открыты не в банке, а в подразделении Банка России.

WEIGHTS = [7,1,3,7,1,3,7,1,3,7,1,3,7,1,3,7,1,3,7,1,3,7,1]

def check_account(bic: str, account: str) -> bool:
    """Контрольный ключ банковского счёта по методике ЦБ РФ."""
    if len(bic) != 9 or len(account) != 20:
        return False
    if not (bic + account).isdigit():
        return False

    rkc = bic[6:9]
    prefix = ('0' + bic[4:6]) if rkc in ('000', '001') else rkc

    total = sum(int(d) * w for d, w in zip(prefix + account, WEIGHTS))
    return total % 10 == 0

Проверяем:

>>> check_account('044525225', '40817810000000000002')
True
>>> check_account('044525225', '40817910000000000002')   # одна цифра изменена
False

Одной изменённой цифры достаточно, чтобы арифметика перестала сходиться. Ровно тот класс ошибок, который возникает при переписывании с фотографии.

Побочный эффект, который я не закладывал. Эта же проверка ловит случай, когда клиент присылает корреспондентский счёт банка вместо своего расчётного. Внешне они неотличимы — двадцать цифр, начинаются похоже, — а платёж по корр. счёту уходит в никуда. Но у него другой префикс в формуле, и ключ не сходится. Отдельную проверку писать не пришлось, она получилась бесплатно.

Номер карты: алгоритм Луна

Возврат на карту — второй по частоте сценарий. Здесь работает алгоритм Луна, тот же, что используют платёжные системы:

def luhn(number: str) -> bool:
    digits = [int(c) for c in number][::-1]
    total = 0
    for i, d in enumerate(digits):
        if i % 2:
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return total % 10 == 0
>>> luhn('4276380012345778')
True
>>> luhn('4276380012345778'.replace('77', '87'))
False

ИНН банка

Для юрлиц и для проверки банка-получателя пригодится контрольная сумма ИНН:

def check_inn10(value: str) -> bool:
    if len(value) != 10 or not value.isdigit():
        return False
    weights = [2, 4, 10, 3, 5, 9, 4, 6, 8]
    control = sum(int(value[i]) * weights[i] for i in range(9)) % 11 % 10
    return control == int(value[9])
>>> check_inn10('7707083893')   # действующий ИНН
True
>>> check_inn10('7707083894')
False

Как это выглядит в форме

Клиент вводит БИК — название банка подставляется само. Вводит счёт — контрольные цифры либо сходятся, либо нет. Если не сходятся, кнопка «дальше» не работает.

Здесь важен нюанс поведения интерфейса. Не предупреждение, не жёлтая плашка «пожалуйста, проверьте» — просто нельзя отправить. Предупреждение человек прокликивает, особенно если уверен в своей правоте. Блокировка заставляет вернуться и посмотреть ещё раз.

Сумму прописью тоже больше никто не пишет: она генерируется из числа. Раньше расхождение между цифрой и прописью было отдельным поводом вернуть заявление на доработку. Теперь такого поля просто нет — а нет поля, нет и ошибки.

Итог: класс ошибок «неправильно переписал реквизиты» исчез не потому, что люди стали внимательнее, а потому что неправильные данные нельзя сохранить.


Машина готовит, решает человек

Соблазн после такого — автоматизировать всё до конца: раз данные проверены, пусть заказ создаётся сам и деньги уходят сами.

Мы этого не сделали, и я считаю это правильным. Контрольная сумма подтверждает, что счёт математически корректен. Она не подтверждает, что это счёт нужного человека, что чек настоящий, а описание проблемы соответствует реальности.

Поэтому менеджер сверяет заявку. Но ему для этого всё разложили: как только клиент отправил форму, в тикете появляется приватный комментарий с ФИО, товаром, суммой и кнопкой «Проверить заявку». По кнопке открывается страница, где заявка разбита на смысловые блоки, реквизиты подсвечены отдельно, а чек и фото проблемы открываются рядом. Любое поле правится на месте; поменял цену — сумма прописью пересчиталась сама.

Разница с «полной автоматизацией» тут не в количестве кликов, а в ответственности. Человек по-прежнему принимает решение о деньгах — просто теперь у него для этого один экран вместо пяти вкладок и звонка в бухгалтерию.

Грабли: поле, которого не было

Теперь про место, где стройная схема сломалась.

Складу нужно ставить задачу на проверку вернувшегося товара. Логика напрашивалась очевидная: курьерская служба приняла посылку → появился номер накладной → забор состоялся → ставим задачу. С первым перевозчиком это работало идеально, и я был доволен собой.

Дошли до второго — и оказалось, что номера накладной у него в наших данных нет вообще. Не «приходит с задержкой», не «в другом формате». Его не существует. Событие, на котором держалась вся ветка, для половины возвратов просто не наступает.

Варианты были такие. Запросить номер через интеграцию — отдельный проект на недели, а возвраты нужны сейчас. Ставить задачу руками для этого перевозчика — абсурд, мы ровно от этого и уходили. Найти другое событие — то, что в итоге и сделали.

И вот честная часть. Нужное событие нашёл не я. Его назвала коллега, которая оформляет эти заборы каждый день: заказ в любом случае переводится в статус «Оформить забор», и этот статус есть всегда, независимо от перевозчика. Развели логику: для одного перевозчика — по статусу, для другого — по номеру отправления, как было. Отдел перепроверил обе ветки на реальных заказах и подтвердил, что задачи складу создаются в обоих случаях.

Два вывода, которые я утащил во все последующие проекты.

Проверяйте каждый источник данных отдельно. Легко принять за данность, что нужное поле придёт всегда, — потому что у первого источника, который ты посмотрел, оно есть. Общая картина, достроенная по одному примеру, разваливается ровно там, где ты не смотрел.

Человек, который выполняет операцию руками, знает про неё больше, чем тот, кто рисует схему. Моя заслуга здесь была не в том, чтобы придумать решение, а в том, чтобы прийти с конкретным вопросом вместо «помогите подумать».

Мелочь на одно поле

Ещё один сюжет, показательный тем, насколько маленькой бывает причина большого раздражения.

По старому порядку платёжное поручение уходило на общий адрес компании. Дальше его надо было переслать клиенту — и это делалось, но не всегда и не сразу. Клиент деньги получал, подтверждения не видел и писал спрашивать.

Теперь бизнес-процесс берёт email, который клиент указал в форме, и платёжка уходит ему напрямую. Изменение — одно поле. Целый класс обращений закрылся.

Такие штуки редко попадают в планы, потому что не выглядят проектом. А по соотношению пользы к трудозатратам обычно бьют всё остальное.

Что в итоге

  • Шагов процесса: 26 → 12. Ручных действий менеджера осталось четыре: отправить ссылку, сверить заявку, скачать заявление, оформить забор и запустить бизнес-процесс.

  • Данные вводит клиент, а не два сотрудника после него.

  • Заявление формируется автоматически, в едином формате, с подписью.

  • Реквизиты проверяются при вводе — ошибочные отправить нельзя.

Проект не закрыт, и я сознательно не пишу «мы всё автоматизировали». Автосоздание заказа в CRM пока отключено: на время обкатки забор оформляем вручную. Дальше — автоотправка клиенту трек-номера и платёжки прямо в чат и дашборд по возвратам, потому что сейчас эти данные никуда не выгружаются и померить процесс нечем.

Что я забрал из этого проекта

Возражение «люди будут ошибаться» — не аргумент против автоматизации, а требование к ней. Оно не означает «оставим как есть». Оно означает «сделайте так, чтобы ошибка не проходила».

Прежде чем автоматизировать, выясните, что вообще обязательно. Мы годами печатали и сканировали заявления, потому что «так положено». Положено оказалось сильно меньше — но чтобы это узнать, надо сходить и спросить юриста, а не гадать.

Проверка данных часто уже существует в стандарте. Контрольные суммы в счетах, картах и ИНН придуманы ровно под этот случай и не стоят ничего. Прежде чем писать свою валидацию или дёргать внешний сервис, стоит посмотреть, нет ли проверки в самом формате данных.

Автоматика готовит, человек подтверждает — там, где на кону деньги. Цель не убрать человека из процесса, а сделать так, чтобы его решение занимало секунды и опиралось на всё нужное сразу.

И главное про сквозные процессы: их невозможно починить, пока у них нет хозяина. Пять систем и шесть отделов не договорятся сами. Техническая часть здесь была меньшей половиной работы — большей были разговоры.