Skip to content

Latest commit

 

History

History
292 lines (238 loc) · 15 KB

File metadata and controls

292 lines (238 loc) · 15 KB

API для внешних интеграций

Приложение не предоставляет сетевой backend API. Под интеграционным API здесь понимаются стабильные форматы обмена данными:

  • JSON-экспорт расчёта;
  • share-link с параметрами расчёта в URL;
  • правила версионирования этих форматов.

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

JSON-экспорт

JSON-файл создаётся кнопкой экспорта в приложении. Текущий формат основан на снимке SharedCalculationV1 и дополнительно содержит exportedAt. Перед созданием JSON приложение проверяет обязательные поля кредита и расчётные ограничения, чтобы не распространять заведомо невалидный payload.

Минимальная структура:

{
  "version": 1,
  "name": "Мой кредит",
  "config": {
    "principal": 7200000,
    "annualRate": 12.4,
    "rateChanges": [],
    "rateChangeMode": "nextPeriod",
    "issueDate": "2026-06-23",
    "firstPaymentDate": "2026-07-15",
    "firstPaymentInterestOnly": true,
    "termMonths": 240,
    "paymentDay": 15,
    "paymentType": "annuity",
    "frequency": "monthly",
    "currency": "RUB",
    "rounding": "kopecks",
    "closeThreshold": 300,
    "oneTimeFee": 0,
    "monthlyFee": 0,
    "earlyRepaymentFeePercent": 0,
    "interest": {
      "method": "daily",
      "dayCountBasis": "actualActual",
      "includePaymentDate": true,
      "periodStart": "exclusive",
      "balanceMoment": "startOfDay"
    }
  },
  "repayments": [],
  "repaymentRules": [],
  "gracePeriods": [],
  "selectedScenario": "combined",
  "settings": {
    "termUnit": "months",
    "displayDecimals": 2,
    "theme": "emerald",
    "customAccentColor": "#0b9873",
    "useCustomAccentColor": false
  },
  "exportedAt": "2026-07-07T10:00:00.000Z"
}

Верхний уровень

Поле Тип Обязательность Описание
version 1 рекомендуется Версия формата снимка. Для старых JSON может отсутствовать; если поле указано, принимается только числовое значение 1.
name string нет Название кредита. Ограничение: 500 символов.
config LoanConfig да Основные параметры кредита.
repayments EarlyRepayment[] нет Разовые досрочные платежи. Если отсутствует, считается пустым массивом.
repaymentRules RepaymentRule[] нет Регулярные правила досрочного погашения. Если отсутствует, считается пустым массивом.
gracePeriods GracePeriod[] нет Льготные периоды. Если отсутствует, считается пустым массивом.
selectedScenario string нет Выбранный сценарий. По умолчанию reduceTerm.
settings object нет Настройки отображения. Для старых JSON допускаются эти поля прямо на верхнем уровне.
exportedAt ISO datetime нет Время создания файла. При импорте используется только как справочное поле.

config

Поле Тип / значения
principal number, больше 0
annualRate number, от 0 до 100
rateChanges RateChange[], максимум 1000
rateChangeMode nextPeriod или exactDate
issueDate дата YYYY-MM-DD
firstPaymentDate дата YYYY-MM-DD, строго после issueDate
firstPaymentInterestOnly boolean
firstPaymentInterestOnlyMode addToTerm или withinTerm; для legacy-файлов по умолчанию addToTerm
termMonths integer, от 1 до 1200
paymentDay integer, от 1 до 31
paymentType annuity или differentiated
frequency monthly, biweekly или quarterly
currency RUB, USD, EUR или CNY; отсутствующее поле получает RUB, известный legacy-код RUR преобразуется в RUB с предупреждением, другие явные значения отклоняются
rounding kopecks, rubles или bank
closeThreshold number, не меньше 0
oneTimeFee number, не меньше 0
monthlyFee number, не меньше 0
earlyRepaymentFeePercent number, от 0 до 100
interest InterestConfig

RateChange:

{
  id: string
  date: string
  annualRate: number
}

date должна быть корректной датой YYYY-MM-DD, позже config.issueDate; даты изменений ставки не должны повторяться.

InterestConfig:

{
  method: 'annuity' | 'daily'
  dayCountBasis: '366' | '360' | 'actual365' | 'actualActual'
  includePaymentDate: boolean
  periodStart: 'inclusive' | 'exclusive'
  balanceMoment: 'startOfDay' | 'endOfDay'
}

Legacy-значение 365 при импорте и миграции persisted state преобразуется в эквивалентное actual365. Поле dayCountBasis используется только при method: 'daily'; периодический метод делит номинальную ставку на число платёжных периодов в году.

repayments

Разовый досрочный платёж:

{
  id: string
  date: string
  amount: number
  enabled?: boolean
  amountMode?: 'extra' | 'totalWithFee'
  sameDaySequence?: number
  operationSource?: 'manual' | 'rule'
  sourceRuleId?: string
  strategy: 'reduceTerm' | 'reducePayment' | 'full' | 'custom'
  source: 'own' | 'subsidy' | 'insurance' | 'other'
  sameDayOrder: 'regularFirst' | 'earlyFirst'
  interestFirst: boolean
  comment?: string
}

Ограничения:

  • максимум 5000 элементов;
  • amount может быть 0, чтобы временно отключить платёж без удаления записи;
  • amountMode: 'totalWithFee' допустим только в дату регулярного платежа и только после регулярного списания;
  • для одной даты допускается только одна активная операция с amountMode: 'totalWithFee' и положительной суммой;
  • sameDaySequence, если указан, должен быть целым числом не меньше 0 и не должен дублироваться в рамках одной даты.

repaymentRules

Правило регулярного досрочного погашения:

{
  id: string
  name: string
  ruleSequence?: number
  type: 'weeklyFixed' | 'monthlyFixed' | 'bimonthlyFixed' | 'quarterlyFixed' | 'semiannualFixed' | 'annualFixed' | 'annualBonus' | 'paymentPercent' | 'monthlyTotalPayment'
  startDate: string
  endDate: string
  amount?: number
  percent?: number
  enabled?: boolean
  strategy: 'reduceTerm' | 'reducePayment' | 'full' | 'custom'
  source: 'own' | 'subsidy' | 'insurance' | 'other'
  sameDayOrder: 'regularFirst' | 'earlyFirst'
  interestFirst: boolean
  skipMonths: string[]
  comment?: string
}

Ограничения:

  • максимум 5000 правил;
  • startDate и endDate должны быть датами YYYY-MM-DD, endDate не раньше startDate;
  • для paymentPercent требуется percent, для остальных типов требуется amount;
  • amount и percent могут быть 0, чтобы временно заморозить правило;
  • monthlyTotalPayment всегда нормализуется к sameDayOrder: 'regularFirst';
  • skipMonths содержит строки YYYY-MM, максимум 1200 элементов.

gracePeriods

Льготный период:

{
  id: string
  startDate: string
  endDate: string
  type: 'full' | 'interestOnly' | 'reduced' | 'custom'
  paymentAmount?: number
  extendTerm: boolean
  accrueInterest: boolean
  capitalizeInterest: boolean
}

Ограничения:

  • максимум 100 элементов;
  • endDate не раньше startDate;
  • периоды не должны пересекаться;
  • paymentAmount, если указан, должен быть неотрицательным.

settings

{
  termUnit: 'months' | 'years'
  displayDecimals: 0 | 2
  theme: 'emerald' | 'ocean' | 'violet' | 'graphite' | 'warm' | 'night'
  customAccentColor?: string
  useCustomAccentColor?: boolean
}

customAccentColor должен быть HEX-цветом вида #0b9873.

Share-link

Share-link хранит тот же снимок расчёта, что и JSON, но без поля exportedAt. Перед созданием ссылки или короткого кода параметров применяется та же проверка, что и для JSON-экспорта.

Формат URL:

https://example.com/CreditCalc/#calc=<payload>

Формат payload:

v1.<base64url(gzip(utf8-json(SharedCalculationV1)))>

Расшифровка:

  1. из URL hash берётся значение после calc=;
  2. проверяется префикс v1.;
  3. часть после префикса декодируется как Base64URL;
  4. байты распаковываются через gzip;
  5. результат читается как UTF-8 JSON;
  6. JSON проходит ту же нормализацию и валидацию, что и импорт из файла.

Приложение также принимает:

  • полный URL с #calc=...;
  • строку calc=...;
  • сырой payload v1.....
  • payload v1...., внутри которого JSON может не содержать собственного поля version.

Ограничения share-link:

  • единый максимальный размер UTF-8 JSON для импорта, JSON-экспорта и данных до/после распаковки share-link: 8 МиБ;
  • максимальная длина закодированного base64url payload: 12 МиБ символов;
  • распаковка контролирует размер потоково и останавливается до выделения чрезмерного буфера.

Версионирование и совместимость

Текущая версия снимка данных: version: 1.

Текущий префикс share-link: v1..

Правила для интеграций:

  • Генерируйте version: 1 для новых JSON-файлов и share-link payload.
  • При чтении JSON не полагайтесь на порядок полей.
  • Игнорируйте неизвестные поля на верхнем уровне и внутри объектов, если они не конфликтуют с известной моделью.
  • Передавайте даты только в формате YYYY-MM-DD, месяцы пропуска только в формате YYYY-MM.
  • Не экспортируйте готовый график платежей как часть модели обмена: он является производным результатом.
  • Для старых данных допускается отсутствие некоторых полей config и config.interest; приложение подставляет значения по умолчанию.
  • Если поле currency отсутствует, приложение использует RUB с предупреждением. Из явных legacy-значений поддерживается только RUR → RUB; суммы не конвертируются. Другие неизвестные валюты отклоняются.
  • Отсутствующие legacy-поля получают документированные defaults. Если финансовое поле присутствует, но содержит значение вне схемы, импорт завершается ошибкой. Преобразования RUR, amountMode: "total" и отсутствующего legacy amountMode возвращаются в importWarnings.
  • Share-link с неизвестным префиксом, например v2., должен считаться неподдерживаемым.
  • JSON или share-link с version, отличной от 1, не следует создавать до появления отдельной миграции формата.
  • Импорт отклоняет JSON с любой явно указанной неизвестной, строковой или будущей версией. Legacy JSON без version трактуется как версия 1; это единственное правило совместимости для отсутствующего поля.
  • Runtime-схема проецирует только документированные поля. Неизвестные свойства удаляются, комментарии обязаны быть строками, а идентификаторы ограничены 128 символами.
  • Повреждённый share payload удаляется из URL hash после первой ошибки, чтобы перезагрузка не запускала его повторно; сообщение показывается глобально с переходом к панели импорта.

При добавлении новой версии формата нужно:

  1. оставить чтение version: 1;
  2. добавить явную миграцию из старой структуры в текущую модель;
  3. сменить префикс share-link только при несовместимом изменении кодирования или структуры payload;
  4. обновить этот документ вместе с кодом сериализации и импорта.