Changelog — Merchant API v1¶
Журнал изменений контракта api/openapi.yaml. Правится только здесь; в портал документации (docs/portal/docs/changelog.md) файл копируется скриптом docs/portal/build.sh.
Формат записи: заголовок ## ГГГГ-ММ-ДД — <версия> и список изменений по группам. Версия совпадает с info.version в api/openapi.yaml. Суффикс -draft означает, что контракт ещё не заморожен (заморозка — критерий выхода из этапа M0); после заморозки любое изменение вносится только через ADR (см. api/README.md).
Группы: Добавлено — совместимое расширение (новое поле, значение enum, операция); Изменено — несовместимое изменение (допустимо только в черновике; в /v1 после заморозки запрещено); Исправлено — описания, примеры, опечатки без изменения поведения; Устарело — элемент помечен deprecated, продолжает работать.
Клиенты обязаны игнорировать неизвестные поля ответов и неизвестные значения справочников (FR-API-011): именно это позволяет расширять контракт без смены версии.
2026-09-14 — 1.0.0¶
Правки описаний без изменения схем: добавленные ограничения return_url и оговорка про одноразовость confirmation_url. Формы полей, коды ответов и обязательность не менялись, поэтому ADR не требуется (api/README.md п. 3) и версия остаётся прежней.
Добавлено
ConfirmationRequestRedirect.return_urlиConfirmationRequestQr.return_url: вdescriptionперечислены ограничения, которые касса уже применяет на входе, — без учётных данных в адресе, без пробелов и управляющих символов, только публичный адрес (непубличные диапазоны и внутренние имена отклоняются). Отказ —400invalid_requestсparameter: confirmation.return_url. Ограничение новое: такие адреса удовлетворяли опубликованной схеме (^https://,format: uri,maxLength: 2048) и до сих пор принимались. Причина — адрес выдаётся не только мерчанту в объекте платежа, но и браузеру плательщика: по нему рисуется кнопка возврата в магазин на странице результата KASSA (FR-CHK-005), то есть произвольная навигация с доменаpay.{domain}. Адрес приводится к канонической форме до проверки, поэтому нестандартная запись IPv4 проверяется по тому, во что она разворачивается:https://2130706433/— это127.0.0.1и будет отклонён, а нестандартная запись публичного адреса принимается и сохраняется канонической. В тестовом контуре (ключtest_) сняты все ограничения по досягаемости адреса — непубличные диапазоны, внутренние имена и любые записи локальных адресов:https://localhost:3000/returnпринимается, мерчант на этапе интеграции разрабатывает локально (FR-API-012). Требованияhttps, отсутствия учётных данных и отсутствия управляющих символов не снимаются ни в одном контуре.PaymentLinkCreate.return_url: то же правило объявлено, но ещё не применяется — операции платёжных ссылок отвечают501 not_implementedдо этапа M5, и проверка появится вместе с ними. Отказ там назовётparameter: return_url, а неconfirmation.return_url: в теле этой операции объектаconfirmationнет,return_url— поле верхнего уровня. Поведение операции этой записью не меняется.Confirmation.confirmation_url: сказано прямо, что адрес возвращается только в ответе на создание платежа. Он содержит одноразовый токен страницы оплаты, в базе кассы лежит лишь его хеш, и восстановить адрес последующимGET /payments/{payment_id}невозможно. Поведение не менялось — контракт о нём молчал.
Исправлено
^https://уreturn_urlначал соблюдаться. Паттерн объявлен записью от 2026-09-10 (п. 5), но генератор клиента теряет его приformat: uri, и правило держало только ограничение базы:javascript:,data:иhttp://доходили доCOMMITи давали500internal_errorвместо400. Теперь —400invalid_requestсparameter, как и обещает справочник ошибок. Схема не менялась.
2026-09-10 — 1.0.0¶
Заморозка состава Merchant API v1 под модель A «касса — получатель платежа» (ADR-0006, ADR-0007, CORRECTION-001-merchant-of-record.md). Пакет из 35 правок сводит воедино TZ FR-API-014 п. 1–15 и §12.2–12.4, CORRECTION-001 §4, финальное ревью контракта и 20 ошибок линтера. Дальнейшие изменения /v1 — только совместимые и только новой записью в этом файле.
Добавлено
- Схема
SignedAmount— сумма с допустимым ведущим минусом; применяется только в ответахBalance.availableиSettlement.net_amount(отрицательный баланс — штатное состояние модели A).Balance.payableостаётся неотрицательнымAmount. - Общая схема
Metadata(до 16 ключей, ключ ≤ 64 символов —MetadataKey, значение ≤ 512 символов —MetadataValue) вместо четырёх расходящихся описаний вPaymentCreate,RefundCreate,PaymentLinkCreate,PayoutCreate. - Пять кодов ошибок TZ §12.4:
limits_exceeded(422, портфельные лимиты),insufficient_merchant_balance(422),payments_suspended(422),evidence_deadline_passed(409),not_implemented(501). Граница сamount_limit(только пер-транзакционные min/max) описана явно; одноимённоеcancellation_details.reason = limits_exceeded— другой справочник. - Полный состав баланса модели A:
Balance.deposit,pending_refunds,disputed,test; все поля обязательны; зафиксировано, чтоavailableуже уменьшен на резерв, возвраты в пути и удержания по спорам. - Схемы
SettlementStatusиFrozenReason— состояние расчётов мерчанта:payouts_frozen,frozen_reason,frozen_at,unfrozen_at,settlement_delay_days,next_settlement_at. Тот же фрагмент возвращается вShop.settlement.statusи является объектом событийsettlement.frozen/settlement.unfrozen. - Поля магазина:
receipt_mode(agent/merchant_issues/none),settlement.reserve— политика rolling reserve массивом по валютам (currency,percent_bps,days),settlement.status;settlement_modelстал обязательным иreadOnly; уShop.idпоявился паттерн^sh_[0-9A-Z]{26}$. - Разделы расчёта:
reserve_held_amount,reserve_released_amount,deposit_withheld_amount,bank_fines_amount,chargeback_fees_amount,disputes_amount,settlement_delay_days,test; границы периодаperiod_from/period_toобъявлены включительными. - Объект спора: схемы
Dispute,DisputeKind,DisputeStatus,DisputeEvidence— раньше событияdispute.*были объявлены, а объекта в контракте не было. - Операции споров (
x-stage: M2, до этапа отвечают501 not_implemented):GET /disputesс фильтрамиpayment_id,status,kind,created_at_gte,created_at_lt;GET /disputes/{dispute_id}; двухшаговая передача файловPOST /disputes/{dispute_id}/evidence/uploads(схемыDisputeEvidenceUploadCreate,DisputeEvidenceUpload) иPOST /disputes/{dispute_id}/evidence(схемаDisputeEvidenceCreate); тегdisputes; параметр путиDisputeId; лимиты доказательств TZ §12.3 (PDF/JPEG/PNG, 10 МБ, 10 файлов, комментарий ≤ 4096). - Три типа событий TZ §12.2:
dispute.evidence_required,settlement.frozen,settlement.unfrozen. - Схема ответа
WebhookEndpointAfterUpdateдляPATCH /webhook-endpoints/{endpoint_id}: приrotate_secret: trueновыйsecretвозвращается один раз — до этого ротация была неосуществима по контракту. - Поля платежа:
merchant_display_name(обязательное, только чтение),statement_descriptor(только чтение),charged_back_amount,pending_refunds_amount,disputed_amount,fee_refunded_amountи производноеrefundable_amount— именно оно ограничивает сумму возврата. - Возврат объектами того, что принимается на входе:
PaymentLinkполучилmin_amount,max_amount,order_id,capture,return_url,metadata,test;Payout—order_id,metadata,test;Refund—test. - Ответ
200на повтор идемпотентного запроса у всех POST (/refunds,/payment-links,/webhook-endpoints,/payouts,/events/{event_id}/resend,/test/payments/{payment_id}/simulate, обе операции доказательств); для операций, чей первый ответ и так200, правило записано вdescription. - Фильтры расчётов
period_from,period_to,currency,status; фильтры событийcreated_at_gte,created_at_lt. - Пары «HTTP + код ошибки» в описаниях
POST /payments,POST /refunds,GET /balance,GET /settlementsи всех операций споров. - Паттерны идентификаторов там, где были свободные строки:
Refund.payment_id,PaymentLink.payments[],Shop.id, все пути споров. Refund.order_id(необязательное,readOnly,maxLength: 128) — по итогам ревью контракта. Касса копирует значение изPayment.order_idвозвращаемого платежа; в запросе поле не принимается. До этогоRefundбыл единственным объектом без идентификатора заказа (он есть уPayment,PaymentLink,Payout,Dispute), и обработчик, написанный по гайду «сопоставляйте с заказом поdata.object.order_id» (docs/portal/docs/webhooks.md§4 п. 4), падал на первом же событииrefund.succeeded/refund.canceled. Совместимое добавление; ожидание рынка —docs/01-competitor-api-analysis.md§10 п. 4.
Изменено
Три исправления черновика, разрешённые TZ FR-API-014 (после этой записи состав /v1 заморожен):
Balance.availableиSettlement.net_amountпереведены сAmountнаSignedAmount(FR-API-014 п. 2): беззнаковый паттерн не выражал долг мерчанта перед кассой.- Из
PaymentCreateудалено полеstatement_descriptor(FR-API-014 п. 8, FR-PAY-011): дескриптор формирует касса и возвращает вPayment.statement_descriptor(только чтение); переданное мерчантом значение молча игнорировалось. - Из
Receiptудалено полеsend_by_kassa(FR-API-014 п. 9, FR-RCP-002): режим фискализации — настройка магазинаShop.receipt_mode, а не поле запроса; одним полем запроса отключался обязательный модуль.
Два сужения типа без изменения поведения (называются явно, чтобы changelog и TZ FR-API-014 не разошлись):
Error.codeпереведён из свободной строки с прозаическим справочником в закрытый enumErrorCode; состав кодов расширен, ни один прежний код не удалён и не переименован.- У исходящих URL мерчанта (
WebhookEndpointCreate.url,WebhookEndpointUpdate.url,ConfirmationRequest.return_urlдляredirectиqr,PaymentLinkCreate.return_url) добавленыpattern: '^https://'иmaxLength: 2048— правило «только https» перенесено из прозы в схему.
Кроме того: Event.data получил обязательный object_type и дискриминатор (EventData, EventDataPayment, EventDataRefund, EventDataPaymentLink, EventDataPayout, EventDataSettlement, EventDataSettlementStatus, EventDataDispute) — тип объекта больше не разбирается по префиксу id; next_cursor внесён в required всех списочных ответов (клиент отличает «конец списка» от «поле не отдано»); Event.test, Payout.test, Refund.test, Settlement.test, Balance.test стали обязательными; info.version — 1.0.0 (снят суффикс -draft).
Три отклонения от CORRECTION-001 §4, принятые сознательно: доля резерва задана в базисных пунктах (percent_bps, а не percent — CLAUDE.md запрещает проценты дробным числом); удержанная сумма резерва живёт в Balance.reserve и Balance.deposit, а не в политике магазина (held_amount в Shop не вводится — политика описывает правило, баланс описывает деньги); политика резерва — массив по валютам, потому что каскад и реестр выплат работают строго в пределах одной валюты.
Исправлено
- Пять описаний во flow-mapping, где незакавыченная запятая создавала ключ-мусор внутри схемы (ошибка
structв redocly):Customer.ip,PaymentCreate.statement_descriptor,RefundCreate.receipt,Settlement.report_url,PayoutCreate.destination.card_token; заодно закавычены описанияCustomer.tax_id,PaymentMethodData.saved/id,CancellationDetails.provider_code,Confirmation.confirmation_url/confirmation_data,WebhookEndpointUpdate.rotate_secret,WebhookEndpointWithSecret.secret,PaymentLinkCreate.amount/reusable. - Одиннадцать операций без
summary:GET /payment-links/{link_id},POST /payment-links/{link_id}/deactivate,GET /events/{event_id},POST /webhook-endpoints,GET /webhook-endpoints,GET|PATCH|DELETE /webhook-endpoints/{endpoint_id},GET /settlements/{settlement_id},GET /payouts,GET /payouts/{payout_id}. - Вебхукам добавлены
operationId(deliverEvent,authorizeRequest) и тегevents; снято наследование глобальногоsecurity: [basicAuth]— доставка вебхука не требует Basic-аутентификации мерчанта. Ключ разделаauthorize_requestпереименован вauthorize-request(kebab-case, требованиеpaths-kebab-case); тип события остаётсяpayment.authorize_request— имя ключа в документе на провод не выходит. - В описание доставки вебхука внесено: подпись считается по сырым байтам тела; в период ротации секрета заголовок несёт две подписи
v1, достаточно совпадения любой; тип объекта разбирается поdata.object_type. GET /balanceбольше не называется «балансы магазина» и не обещает пустой список в технологической модели: значения — уровня мерчанта, модель по умолчанию —agent.Customer.ipиPaymentCreate.client_ipограничены 45 символами;ReceiptItem.supplier.*— длинами полей, а описание приведено к режимуShop.receipt_mode = agent.-
Все инлайновые объекты и перечисления вынесены в именованные схемы
components/schemas(по итогам ревью контракта). Формат JSON не изменился ни в одном месте: правится только структура документа, поэтому запись здесь, а не в группе «Изменено». Проверено сравнением полностью раскрытых (dereferenced) схем всех операций до и после правки — единственное отличие раскрытых схем — новое полеRefund.order_id.Причина:
datamodel-codegenдаёт инлайновым схемам имена по имени поля и разводит совпадения числовым суффиксом. ИнлайновыйShop.settlementзанимал имяSettlement, а настоящая схема расчёта становиласьSettlementModel1— рядом сSettlementModel(перечислениеagent/technology) иSettlementStatus(состояние заморозки);Receipt.customerзанимал имяCustomer1при существующей схемеCustomer;PaymentLink.payments[]давалPayment1; семь инлайновых перечисленийstatusпревращались вStatus…Status6. Код M1–M5 импортировал быSettlementModel1, имея в виду выписку. TS-клиента дефект не касался (openapi-typescriptсохраняет имена компонентов) — он был виден только в Python.Новые схемы:
SettlementModel,ReceiptMode,ReservePolicy,ShopSettlement,ShopCapabilities,Locale,ConfirmationRequestRedirect,ConfirmationRequestQr,ConfirmationRequestEmbedded,ConfirmationType,CardBrand,CardDetails,SbpDetails,CancellationParty,CancellationReason,ReceiptPaymentSubject,ReceiptPaymentMode,ReceiptItemSupplier,ReceiptCustomer,ReceiptState,PaymentMethodRequest,ThreeDSecureDetails,AuthorizationDetails,PaymentId,PaymentLinkStatus,EventDeliveryStatus,EventDelivery,WebhookEndpointDesiredStatus,WebhookEndpointStatus,SettlementState,EvidenceContentType,EvidenceFileId,PayoutDestinationCardToken,PayoutDestinationSbp,PayoutDestinationBankAccount,PayoutDestination,PayoutStatus. Заодно устранены четыре дубля: тип файла доказательства (EvidenceContentType) иSettlementState(в схемеSettlementи в фильтреGET /settlements) описаны по одному разу.PaymentIdиEvidenceFileIdприменяются только там, где инлайновый тип породил бы безымянную модель, — в элементах массивов и в параметре путиpayment_id; в скалярных полях объектов паттерн остаётся записанным на месте, чтобы генератор давалstr, а не обёрткуRootModel.
2026-09-10 — 1.0.0-draft¶
Первая публикация черновика контракта Merchant API v1 (ADR-0003 «Стиль публичного Merchant API»).
Добавлено
- Аутентификация HTTP Basic
shop_id:secret_key; ключиtest_/live_с регионом (test_ru_…,live_kz_…); секрет показывается один раз. - Заголовок
Idempotency-Key(алиасIdempotence-Key) на всех POST и PATCH; срок действия 24 часа; конфликты409 idempotency_conflictи409 idempotency_in_progress. - Ресурсы:
/shop;/payments(создание, список, чтение,capture,cancel);/refunds;/payment-links(создание, список, чтение,deactivate);/payment-methods;/events(список, чтение,resend);/webhook-endpoints(создание, список, чтение, изменение с ротацией секрета, удаление);/settlements;/balance;/payouts(x-stage: M5);/test/payments/{payment_id}/simulate(только тестовые ключи). - Объект
Amount {value, currency}— строка с двумя знаками после точки; валютыRUB,KZT,USD,EUR. - Статусы платежа
pending → waiting_for_capture → succeeded | canceled, возвратаpending → succeeded | canceled; справочникcancellation_details.reason(TZ §12.1). - Способы оплаты
bank_card,sbp,kaspi,mir_pay,sber_pay,t_pay,apple_pay,google_pay,installments; в ответе — только маскированные данные карты (first6,last4,brand). - Подтверждение оплаты
redirect(чекаут KASSA),qr,embedded(M5); в ответе —confirmation_urlилиconfirmation_data. - Чеки: объект
Receiptс профилямиru(ФФД 1.2) иkz;receipt_statusв платеже и возврате. - События (TZ §12.2) и вебхуки: заголовки
X-Kassa-Event-Id,X-Kassa-Signature: t=<unix>,v1=<hmac_sha256>; окно проверки 300 с; повторы 1 м, 5 м, 15 м, 1 ч, 4 ч, 12 ч, 24 ч, 48 ч, 72 ч; синхронный вебхукpayment.authorize_request. - Единый формат ошибок
Error {type, code, description, parameter?, request_id}и справочник кодов вError.code. - Курсорная пагинация (
limit1–100, по умолчанию 20;cursor;next_cursor); заголовокX-Request-Idв каждом ответе; временные метки ISO 8601 в UTC с суффиксомZ.