Перейти к основному содержимому

@mineflow/shared-validation

Frontend-safe проверки бизнес-форматов Республики Казахстан: контрольные суммы ИИН, БИН и mod-97 для KZ IBAN. Чистые функции без зависимостей — работают одинаково в браузере и React Native.

Что это и зачем

Пакет относится к слою L0 SDK (ADR-0042) — тот же уровень, что и @mineflow/api-zod. Но если api-zod codegen'ится из OpenAPI и проверяет структуру формы (типы, длины, обязательность), то checksum-проверки в схему не попадают: OpenAPI описывает iin как string длиной 12, но не знает алгоритм контрольной цифры НК РК. shared-validation закрывает именно этот зазор — семантическую корректность идентификаторов, которую нельзя выразить в JSON Schema.

Функции выделены в отдельный пакет, потому что один и тот же ИИН/БИН валидируется в нескольких местах: scm/suppliers (БИН организации, ИИН ИП), hr/personnel (ИИН сотрудника) и любых будущих формах. Это закрывает open-question Q17 модуля scm/suppliers (ADR-0028). Пакет zero-runtime-dependency и sideEffects: false — tree-shaking забирает только реально вызванные функции.

Установка

pnpm add @mineflow/shared-validation
примечание

У пакета нет peer- и runtime-зависимостей. mod-97 для IBAN считается итеративно, без BigInt, чтобы оставаться совместимым с любым target и любой средой исполнения. В монорепо MineFlow резолвится как workspace:*.

Использование

Все три функции принимают string и возвращают boolean. Они не бросают исключений и сами защищаются от не-строк, неверной длины и посторонних символов — невалидный вход просто даёт false.

import {
validBinChecksum,
validIbanMod97,
validIinChecksum,
} from '@mineflow/shared-validation';

validIinChecksum('900101350510'); // true | false по контрольной цифре
validBinChecksum('123456789012'); // true | false
validIbanMod97('KZ75125KZT2069100100'); // true | false (20 символов, начинается с 'KZ')

В Zod-форме через .refine()

Главный сценарий — дополнить структурную схему из api-zod checksum-проверкой, которой там нет. Накладываем .refine() поверх готового поля:

import { z } from 'zod';
import { validBinChecksum, validIinChecksum } from '@mineflow/shared-validation';

const supplierForm = z.object({
name: z.string().min(1),
// структуру (12 цифр) проверяет regex, контрольную сумму — refine
bin: z
.string()
.regex(/^\d{12}$/, 'БИН — это 12 цифр')
.refine(validBinChecksum, { message: 'Неверная контрольная сумма БИН' }),
iin: z
.string()
.regex(/^\d{12}$/, 'ИИН — это 12 цифр')
.refine(validIinChecksum, { message: 'Неверная контрольная сумма ИИН' }),
iban: z
.string()
.refine(validIbanMod97, { message: 'Неверный IBAN (mod-97)' }),
});
подсказка

Держи .regex()/структуру до .refine(): тогда пользователь сперва увидит понятное «12 цифр», а сообщение о контрольной сумме — только когда формат уже верный. Так как функции возвращают false на любой мусор, порядок ещё и страхует от шумных сообщений на полупустом поле.

Если работаешь с codegen-схемой из api-zod напрямую, расширяй её тем же приёмом — zCreateSupplierDto.refine(...) или через .extend({ bin: ... }). Подключение схемы к react-hook-form через zodResolver и обработку ошибок полей смотри в рецепте Формы.

Ключевые экспорты

ЭкспортСигнатураЧто проверяет
validIinChecksum(iin: string) => booleanИИН РК: 12 цифр, контрольная цифра по алгоритму НК РК (веса w1, при результате 10 — пересчёт по w2)
validBinChecksum(bin: string) => booleanБИН РК: тот же алгоритм, что и ИИН (12 цифр, w1 → при 10 пересчёт по w2)
validIbanMod97(iban: string) => booleanKZ IBAN: ровно 20 символов, начинается с KZ, проверка ISO 13616 mod-97 (остаток должен быть 1)

Полный перечень и сигнатуры — в API-референсе из кода.

Поведение валидаторов

  • validIinChecksum / validBinChecksum — алгоритм идентичен (разнесены лишь для семантической чистоты: ИИН — физлицо/ИП, БИН — организация). Считают sum(d_i * w1_i) mod 11 по первым 11 цифрам; если результат < 10 — это контрольная цифра. Если результат == 10, пересчёт по второму набору весов w2; если и он даёт 10 — идентификатор невалиден.
  • validIbanMod97 — заточен под KZ: длина строго 20 и префикс KZ зашиты в реализацию. Переставляет первые 4 символа в конец, заменяет буквы числами (A=10 … Z=35) и считает mod-97 итеративно. Валиден, когда остаток равен 1.

Подводные камни

Функции принимают только нормализованный ввод

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

  • IBAN из UI обычно приходит как KZ75 125K ZT20 6910 0100 — перед проверкой убери пробелы и приведи к верхнему регистру: iban.replace(/\s/g, '').toUpperCase().
  • ИИН/БИН — убери всё, кроме цифр, если в поле возможны разделители.

Делай нормализацию в Zod через z.string().transform(...) до .refine(...), чтобы валидатор всегда получал чистое значение.

IBAN-проверка только для Казахстана

validIbanMod97 хардкодит длину 20 и префикс KZ — это не общий валидатор IBAN произвольной страны. Любой не-KZ IBAN вернёт false. Для MineFlow это by design: контрагенты — резиденты РК.

Это не замена backend-валидации

Checksum-проверки на фронте — UX-слой (мгновенная подсказка в форме). Backend (scm/suppliers, hr/personnel) валидирует те же инварианты независимо (см. SCM-SUP-INV-06). Frontend-проверка не отменяет серверную и не является источником истины.

React Native

Пакет полностью frontend-safe: чистые функции, нет обращений к window/document, нет BigInt, нет нативных модулей. Импорт и вызовы в RN идентичны вебу:

import { validIinChecksum } from '@mineflow/shared-validation';

Подробнее об ограничениях SDK в RN-окружении — рецепт React Native.

Ссылки