Sprint 11 — каркас для интеграции с операторами фискальных данных РК.
Реальные ApiKey'и появятся у user'а позже; задача — построить такой
фрейм, чтобы подключение оператора сводилось к вписыванию кредов в UI
без правок кода/деплоя.
Что сделано:
- IFiscalProvider (Application/Common/Fiscal) + FiscalResult,
FiscalProviderKind (None/Mock/Webkassa/Kassa24/OfdSolo),
IFiscalProviderFactory, FiscalNotConfiguredException.
- 4 реализации в Infrastructure/Fiscal:
• MockFiscalProvider — фейк MOCK-<8hex> через 300мс, идемпотентный
по Sale.Id (используется dev/stage и интеграционными тестами);
• WebkassaProvider — полный HTTP-pipeline Authorize→Check, парсинг
JSON-ответа, NDS-в-ставке, retry-safe через ExternalCheckNumber;
• Kassa24Provider / OfdSoloProvider — скелет с тем же контрактом,
RegisterAsync бросает FiscalNotConfiguredException (нужны
спецификации API от user'а, NDA-only).
- Миграция Phase11a: 5 колонок в retail_sales (FiscalNumber, QrCode,
Url, ProviderTxId, ProviderKind) + 5 в organizations (FiscalProvider
NOT NULL default 0, ApiKey/Secret encrypted, CashboxUniqueNumber,
ApiBaseUrl). Default 0 = обратная совместимость, существующие чеки
и продажи без фискализации работают как раньше.
- RetailSalesController.Post — TryFiscalizeAsync после commit'а
stock-транзакции. Best-effort: сетевые/HTTP-ошибки логируются, чек
остаётся проведённым. Идемпотентность по IsNullOrEmpty(FiscalNumber).
- OrgFiscalSettingsController: GET/PUT настройки + GET /providers
(опции для select'а) + POST /test-send (фейк-чек к выбранному
провайдеру, не сохраняет в БД).
- UI: FiscalSection в OrganizationSettingsPage с password-input'ами
для ApiKey/Secret (шифруются DataProtection.purpose=foodmarket.fiscal,
в GET — только has-* флаги), спец-значение "__clear__" для снятия,
кнопка «Тестовая отправка».
- Тесты: 11 unit (Mock 5 + Webkassa payload 6) + 3 integration
(Mock сохраняет FiscalNumber, test-send даёт MOCK-номер, None
не фискализует).
- docs/ofd-integration.md — гид с архитектурой, шагами подключения
Webkassa (полный pap), TODO для Касса24/ОФД-Соло, безопасностью
кредов, retry-сценариями.
Все 68 unit + 8 integration в Fiscal/Loyalty/RetailOversell — зелёные.
Web vite build — зелёный.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.1 KiB
Sprint 11 — ОФД-scaffolding (фискализация РК)
Цель: построить фрейм для интеграции с операторами фискальных данных Казахстана (Webkassa / Касса24 / ОФД-Соло), чтобы как только пользователь получит реальный ApiKey — провайдер «оживал» одной настройкой в UI, без правок кода/деплоя. Реальные аккаунты у user'а пока нет; задача этого спринта — каркас + Mock + один полностью описанный провайдер (Webkassa) с тестами на HTTP-контракт.
Старт: 2026-06-07. Исполнитель: Claude Opus 4.7 (автономный режим).
Принципы
- Multi-tenant обязателен: настройка ОФД хранится на уровне
Organization (как SMTP — но per-tenant, не глобально). API-ключи
шифруются через DataProtection (purpose=
foodmarket.fiscal). - Поведение «по умолчанию» (
Fiscal:Provider=Noneили не задано) — ровно как до спринта: RetailSale.Post не зовёт никакого провайдера, FiscalNumber остаётся пустым. Это даёт обратную совместимость. - Каждый пункт: build + локальные тесты +
~/deploy-stage.sh+ retest. - НЕ трогать:
global.json, прод-стек, POS WPF.
Чек-лист
- 1. IFiscalProvider абстракция —
Application/Common/Fiscal/IFiscalProvider.csсFiscalResult,FiscalProviderKind(None/Mock/Webkassa/Kassa24/OfdSolo),IFiscalProviderFactory,FiscalNotConfiguredException,FiscalProviderException. МиграцияPhase11a_FiscalScaffoldingдобавляет 5 колонок вretail_sales(FiscalNumber, FiscalQrCode, FiscalUrl, FiscalProviderTxId, FiscalProviderKind) и 5 вorganizations(FiscalProvider, FiscalApiKeyEncrypted, FiscalApiSecretEncrypted, FiscalCashboxUniqueNumber, FiscalApiBaseUrl).FiscalProviderNOT NULL с default 0 (None) — обратная совместимость.RetailSalesController.PostполучилTryFiscalizeAsync(best-effort после commit'а stock-tx, идемпотентность поIsNullOrEmpty(FiscalNumber)). - 2. MockFiscalProvider —
Infrastructure/Fiscal/MockFiscalProvider.cs, имитация 300мс задержки, детерминированный фейкMOCK-<8hex>отSale.Id. 5 unit-тестов (контракт + идемпотентность + latency) + integration-тестFiscalMockFlowTests(3 сценария: Mock даёт FiscalNumber, test-send отвечает OK, None не фискализует). - 3. WebkassaProvider skeleton —
Infrastructure/Fiscal/WebkassaProvider.cs, полный HTTP-flowAuthorize → Check, парсинг JSON-ответа Webkassa. Token берётся каждым вызовом (TTL-кеш — следующий спринт).BuildCheckPayloadpublic для тестируемости. 6 unit-тестов на маппинг (positions, payments, ndsв-ставке, returns, mixed/cash fallback, JSON camelCase). Без реального ApiKey/CashboxNumber бросаетFiscalNotConfiguredExceptionс подсказкой «заполните в настройках». - 4. Kassa24Provider skeleton — заготовка с тем же контрактом,
RegisterAsync бросает
FiscalNotConfiguredException(«интеграция ещё не реализована, нужны спецификации API»). Подробности — в docs. - 5. OfdSoloProvider skeleton — аналогично.
- 6. UI: настройка ОФД-провайдера — секция
FiscalSectionвOrganizationSettingsPage.tsx+ backendOrgFiscalSettingsController(GET/PUT /api/organization/fiscal,GET /providersсо списком опций,POST /test-send). Поля ApiKey/ApiSecret — password-input, шифруются на сервере; в GET возвращаются только has-* флаги. Спец-значение"__clear__"— снять креды. Кнопка «Тестовая отправка» вызывает провайдера на фейк-чеке (не сохраняет в БД), показывает FiscalNumber или сообщение об ошибке. - 7. docs/ofd-integration.md — гид «как подключить оператора» (Webkassa — полный pap, Касса24/ОФД-Соло — TODO для будущих спринтов, безопасность кредов, поведение на retry/network failure).
Журнал
2026-06-07 старт
Sprint 10 закрыт (4/4 ✓). Поехали по ОФД-чек-листу.
2026-06-07 п.1–п.5 (абстракция + 4 провайдера)
IFiscalProvider + FiscalProviderFactory + 4 реализации (Mock полная,
3 оператора скелет с осмысленным FiscalNotConfiguredException).
Миграция Phase11a добавила 10 колонок в retail_sales + organizations.
RetailSalesController.Post — TryFiscalizeAsync после commit'а.
Тесты: 11 unit (Mock + Webkassa payload) + 3 integration. Все зелёные.
2026-06-07 п.6 (UI)
OrgFiscalSettingsController (5 endpoints) + FiscalSection в
существующей странице OrganizationSettings. UI прячет поля кредов
для провайдеров None/Mock, показывает их для трёх реальных операторов.
Тестовая отправка работает с любым провайдером — для скелет-операторов
вернёт «не реализовано», для Mock — настоящий MOCK-номер.
2026-06-07 п.7 (docs)
docs/ofd-integration.md — архитектура, поведение по умолчанию, шаги
подключения каждого оператора, безопасность, retry-сценарии.
Итог
Все 7 пунктов ✓. Suite-тесты:
- 68/68 unit (включая 11 новых для Fiscal).
- 8/8 integration (Fiscal + Loyalty + RetailOversell в одной группе).
- Web
vite buildзелёный, TS — без ошибок.
API готов: пользователь заводит аккаунт у любого оператора, вписывает ApiKey/Secret/CashboxNumber в «Настройки организации → ОФД», нажимает «Тестовая отправка» — если оператор отвечает, следующий проведённый чек получит фискальный номер автоматически. Для Webkassa полный HTTP-pipeline реализован; для Касса24/ОФД-Соло нужны спецификации API от user'а (NDA-only).