Биллинг подписок mono (зонтик): контракт billing + ledger2 + coopback + UI — НЕ мерджить, стек на ревью #129

Open
claude wants to merge 21 commits from provider-billing into dev
Owner

Зонтичный PR биллинг-стека mono в dev — чтобы стек не терялся.

Структура стека:

  • devэтот PR (provider-billing: Epic 12 контракт billing + w.wal.bill + cron + desktop, Epic 8 реестр коопов) ← #49 (Epic 13 package/PowerUp + Epic 14 mono-сторона)
  • Зеркальный стек в provider: зонтик provider-billing→dev + #25 + #26.

Свежие правки 2026-06-11 (в #49): on-chain anti-replay (таблица payments), billing::pay авторизует _provider, cron-цепочка invoice→pay→confirm, реактивный BillingPaymentListener, выпилен BILLING_CRON_PAYER. Контракт требует пересборки wasm/ABI.

Статус: ревью/правки; мерджить только после явного решения. Схема потока: blago проект 43, требование d9.

🤖 Generated with Claude Code

Зонтичный PR биллинг-стека mono в dev — чтобы стек не терялся. **Структура стека:** - `dev` ← **этот PR** (`provider-billing`: Epic 12 контракт billing + w.wal.bill + cron + desktop, Epic 8 реестр коопов) ← #49 (Epic 13 package/PowerUp + Epic 14 mono-сторона) - Зеркальный стек в provider: зонтик provider-billing→dev + #25 + #26. **Свежие правки 2026-06-11 (в #49):** on-chain anti-replay (таблица payments), billing::pay авторизует _provider, cron-цепочка invoice→pay→confirm, реактивный BillingPaymentListener, выпилен BILLING_CRON_PAYER. Контракт требует пересборки wasm/ABI. **Статус:** ревью/правки; мерджить только после явного решения. Схема потока: blago проект 43, требование d9. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude added 21 commits 2026-06-11 06:20:26 +00:00
feat(provider): реестр кооперативов в Восходе с данными провайдера (Epic 8)
Typecheck / desktop (pull_request) Successful in 12m49s
Typecheck / controller (pull_request) Successful in 13m21s
2afc9b6fec
Прокидывает панель провайдера в страницу «Реестр кооперативов» оператора.

coopback:
- ProviderService.getCooperativesRegistry() — сводит on-chain список
  кооперативов (getAllRows registrator.coops) с подписками провайдера
  (getUserSubscriptions по coopname; провайдер недоступен/без подписки →
  пустой массив, реестр не падает).
- GraphQL query getCooperativesRegistry (chairman/member) + CooperativeRegistryItemDTO.
- BlockchainModule подключён в ProviderModule.
- Регенерированы schema.gql + zeus (controller).

sdk:
- Queries.System.GetCooperativesRegistry (selector + query) + zeus.

desktop:
- Страница реестра грузится через бэкенд (GraphQL) вместо прямого fetchTable
  из блокчейна; раскрытие строки = подписки/инстанс/биллинг; сведение
  on-chain статуса (active/pending/blocked) и backend-статуса инстанса;
  колонка стоимость/мес. Мутации активации/блокировки пока on-chain с фронта.

Проверено: controller tsc, sdk typecheck+build, desktop build:spa — зелёные.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stories 12.1–12.3 (контракты/ledger2):

12.1 — кошелёк w.wal.bill (USER_SHARED, L3) в реестре ledger2:
  - ledger2_wallets::BILLING_FUND_PAY = "w.wal.bill", «Членские взносы пайщика
    на оплату подписок»; реестр 15→16, маппинг программ 6→7 (program_id=1, ЦК).

12.2/12.3 — две новые операции ledger2 (инвариант: одна операция = одна пара
кошельков, существующие не переиспользуются):
  - o.bil.fund (CONVERT): TRANSFER w.wal.share → w.wal.bill, Dr 80 / Cr 86 —
    трансляция паевого взноса в членский на биллинг-кошелёк (зеркало o.sov.axncnv,
    но в персональный кошелёк, а не кооперативный пул);
  - o.bil.pay (PAY): TRANSFER w.wal.bill → w.sov.infra, без проводки (оба разреза
    счёта 86) — оплата подписки членскими взносами.
  - процессы p.bil.fund / p.bil.pay.

12.3 — минимальный контракт billing (ровно два действия):
  - convert(coopname, username, amount, document2): require_auth(coopname),
    presence-only проверка плательщика-кооператива (по требованию @ant — без
    статуса), обязательное подписанное пайщиком заявление (document2, непустой
    hash), один Ledger2::apply(o.bil.fund);
  - pay(coopname, username, amount, payment_hash, memo): require_auth(coopname),
    presence-only проверка кооператива, идемпотентность по payment_hash
    (таблица payments), Ledger2::apply(o.bil.pay); подписок on-chain не хранит.
  - billing добавлен в contracts_whitelist, в сборку (CMakeLists), в boot
    (контракт + аккаунт с eosio.code).

Проверено: ledger2 и billing собираются под CDT 4.1.0 (static_assert реестров
проходят; billing.wasm + ABI: actions convert/migrate/pay, table payments).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Разблокирует coopback-cron (12.6) и desktop UI (12.7): на этом слое появляется
типизированный путь cooptypes → controller → @coopenomics/sdk для contract `billing`.

cooptypes (@coopenomics/cooptypes):
  - contracts/billing/{actions,tables}: обёртки convert/pay/migrate + таблица
    payments; interfaces/billing.ts (IConvert/IPay/IMigrate/IPayment/IDocument2);
  - регистрация _billing в common/names и BillingContract в contracts/index.ts;
  - wallets.generated.ts перегенерён из wallets.hpp (wallets 16, mapping 7).

controller (@coopenomics/controller):
  - domain/billing/ports/billing-blockchain.port.ts (BILLING_BLOCKCHAIN_PORT);
  - infrastructure/blockchain/adapters/billing-blockchain.adapter.ts — подпись
    coopname@active (WIF из vault), account/action из cooptypes; прямой transact
    по прецеденту ledger2-walmove (операторские one-shot операции вне pending-pool);
  - application/billing: мутации billingConvert (роль user/member/chairman, несёт
    document2-заявление) и billingPay (роль chairman, идемпотентно по payment_hash);
  - schema.gql + zeus перегенерены (generate-schema + generate-client, +56/+280 строк,
    только billing).

sdk (@coopenomics/sdk):
  - Mutations.Billing.{Convert,Pay} + selectors/billing/billingResultSelector;
  - регистрация домена Billing в mutations/index.ts.

CLAUDE.md: снято ограничение «generate-schema/generate-client не запускать локально»
(по указанию @ant — генерация отрабатывает локально при поднятом controller'е).

Проверки: cooptypes/controller/sdk — tsc --noEmit чисто; cooptypes и sdk собраны
(unbuild); schema/zeus-diff только billing, без постороннего шума.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Oracle-паттерн (Antelope без deferred_trx — тик инициирует backend):
  - BillingProviderClient — HTTP к provider backend (server-secret): GET
    billing-summary + POST confirm-batch-payment; provider_base_url из конфига;
  - BillingCronService — на тик (config BILLING_CRON_EXPRESSION) обходит коопы
    (BILLING_CRON_COOPNAMES или COOPNAME узла), берёт сумму к оплате у провайдера,
    при total>0 и подошедшем next_payment_due проводит on-chain billing::pay через
    BILLING_BLOCKCHAIN_PORT, затем подтверждает платёж провайдеру;
  - инварианты: сумма=0 → списание не гоняется; идемпотентность по payment_hash;
    падение pay (нехватка на w.wal.bill) логируется и не зацикливает тик; guard от
    наложения тиков; cron off по умолчанию (BILLING_CRON_ENABLED=false).

Плательщик (BILLING_CRON_PAYER) — пайщик, чей USER_SHARED-кошелёк w.wal.bill
дебетуется: списание per-пайщик, без указанного плательщика тик пропускается
(точка конфигурации до решения «кто платит за инфраструктуру коопа»).

config: BILLING_CRON_ENABLED/EXPRESSION/COOPNAMES/PAYER (+ объект config.billing).

Проверка: controller tsc --noEmit чисто.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Готовит данные/документ для UI Восхода (форма конвертации + реестр коопов):

Документ-заявление (Благорост-flow, по образцу 51.ConvertToAxonStatement):
  - cooptypes registry 1095.BillingConversionStatement (Action{convert_amount},
    Meta, Model, title, HTML-context + ru-переводы: «прошу транслировать паевой
    в членский на биллинг-кошелёк… согласие на рекуррентные списания»);
  - factory: Actions/Templates 1095 + регистрация (Actions/Templates/registry/
    index, factories-map). GenerateDocument теперь умеет registry_id=1095 без
    отдельного резолвера.

getBillingSummary (проекция provider-агрегатора в GraphQL для реестра коопов):
  - controller: BillingSummary/BillingSummaryItem ObjectType + Query
    getBillingSummary(coopname, period) (роль chairman/member) → BillingProviderClient;
  - schema.gql + zeus перегенерены;
  - sdk: Queries.Billing.GetBillingSummary + billingSummarySelector.

Проверки: cooptypes/factory/controller/sdk — tsc --noEmit чисто; cooptypes,
factory, sdk собраны (dist), Mutations.Billing.* + Queries.Billing.* в SDK.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ЛК пайщика (форма конвертации, AC#1):
  - features/Billing/ConvertToBilling: api (Mutations.Billing.Convert) + composable
    (generate заявление по registry 1095 → sign WIF пайщика → billingConvert),
    диалог (превью document2 + подпись), кнопка-триггер; isVisible — синглтон;
  - встроено в pages/User/WalletPage (кнопка в header + диалог).

Реестр кооперативов (AC#2/#3):
  - entities/Billing: загрузка Queries.Billing.GetBillingSummary;
  - widgets/Billing/CooperativeBillingPanel: сумма к оплате/за период, дата
    следующего платежа, разбивка подписок с метками free/освобождён, кнопка
    «Оплатить/Продлить» (диалог) → features/Billing/PaySubscriptions
    (Mutations.Billing.Pay);
  - встроено в раскрытие строки pages/Union/ListOfCooperatives (доступ
    chairman/member — как у самой страницы реестра).

ОТКРЫТО: плательщик billing::pay (USER_SHARED w.wal.bill дебетуется per-пайщик)
указывается явно в диалоге оплаты — та же конфиг-точка, что BILLING_CRON_PAYER.

Типы строго из @coopenomics/sdk; паттерны (generate→sign→mutation, client.Query)
зеркалят features/Branch/SelectBranch. SDK/cooptypes-экспорты проверены (resolve).
Полный vue-tsc по desktop не гоняется (правило проекта) — слой собран против
проверенных типов SDK.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	components/desktop/src/pages/User/WalletPage/ui/WalletPage.vue
Reviewed-on: #36
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
# Conflicts:
#	components/desktop/src/pages/Union/ListOfCooperatives/ui/UnionPageListOfCooperatives.vue
Убирает «(Epic 12)» из инлайн-комментариев wallets.hpp и сокращает human_name +
program_label кошельков w.wal.share/w.wal.member/w.wal.bill до коротких форм,
пригодных для UI. wallets.generated.ts перегенерирован из C++.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- pay.cpp: убирает локальный payments_index (idempotency check + emplace);
  дедуп payment_hash переехал на provider (POST /billing/payment-confirmed
  идемпотентен по hash). Контракт billing своих таблиц больше не ведёт.
- convert.cpp: после Ledger2::apply зовёт Soviet::make_complete_document
  (newsubmitted + newresolved в soviet) — подписанное заявление пайщика
  попадает в общий реестр документов кооператива, как в registrator::regcoop
  и capital::createpinv.
- Удалён lib/domain/table_billing_payments.hpp + include в domain/index.hpp.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- config.ts: BILLING_CRON_ENABLED → BILLING_HUB_MODE. Этот же флаг включает
  весь BillingModule (cron, resolver, provider-client). Один источник истины
  включения биллинга.
- app.module.ts: BillingModule условно регистрируется в imports только при
  BILLING_HUB_MODE=true. На спицах флаг false → модуль вообще не подключается,
  GraphQL Billing.*, крон и provider-client не появляются.
- billing-cron.service.ts: убран локальный guard if(!cron_enabled) return —
  избыточен, модуль либо подключён целиком, либо вообще не существует.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5-й аргумент action billing::convert: вместо неявной привязки к document.hash
вводим явный convert_hash — детерминированный sha256 от (coopname, username,
amount, anchor). Он используется:

- как process_hash ledger2-операции (3-й аргумент Ledger2::apply);
- как package_hash в Soviet::make_complete_document — по нему документ
  находится в реестре кооператива и проверяется на повторное завершение
  (newresolved-идемпотентность).

Это связывает «процесс конвертации» в единый якорь: и финансовая операция, и
подписанное заявление пайщика, и поиск документа в реестре — все смотрят на
один convert_hash. Контракт по-прежнему без своих таблиц (Single-Hub v5).

Breaking: меняется сигнатура convert → перегенерация cooptypes/sdk/factory
обязательна; mono coopback/desktop переходят на новый параметр следующим
коммитом после ARM-сборки контракта.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4-й аргумент Soviet::make_complete_document — name бизнес-действия, по которой
документ ищется в общем реестре кооператива. Generic '"convert"_n' пересекается
с любыми другими 'convert' в системе (soviet::converttoaxn и т.п.).

Используем готовое уникальное имя ledger2-операции
operations::billing::CONVERT (= 'o.bil.fund'_n), как и в Ledger2::apply выше.
Конвенция совпадает с capital (Names::Capital::REGISTER_CONTRIBUTOR),
wallet (Names::WalletActions::SIGN_AGREEMENT) — namespaced unique action-name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ответ на review @ant PR #40:

1) BILLING_CRON_COOPNAMES выпилен — список коопов для тика биллинга всегда
   доступен в on-chain таблице registrator.coops. BillingCronService теперь
   запрашивает его через ProviderService.getCooperativesRegistry() с фильтром
   status === 'active'. Никаких CSV-списков в env.

2) Специализированный DTO документа 1095.BillingConversionStatement (канон
   documents-dto/*-document.dto.ts, как convert-to-axon-statement-document.dto):
   - Base/Generate/Signed three-class layout с типизированной meta.convert_amount;
   - billing-convert-input.dto: document — BillingConversionStatementSigned*,
     не generic SignedDigitalDocumentInputDTO;
   - billing.resolver: добавлена mutation generateBillingConversionStatement
     (аналог generateConvertToAxonStatement) с throttle 3/min, roles member/chairman;
   - billing.service.generateConversionStatement: устанавливает registry_id из
     Cooperative.Registry.BillingConversionStatement, форматирует convert_amount
     через AmountFormatterUtils, зовёт documentDomainService.generateDocument;
   - BillingModule подключает DocumentDomainModule.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Дубль метода был в billing.service и ledger2.service. По правилу
mono — 2-кратное повторение → shared. Один источник истины:
`~/shared/utils/transaction.utils.ts` с TransactionUtils.extractTransactionId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reviewed-on: #40
- controller/sdk Zeus: регенерация schema.gql + zeus/{const,index}.ts под актуальный coopback (DTO 1095.BillingConversionStatement, registrator, billing-invoice).
- controller billing.service / provider.service / cooperative-registry-item DTO: правки под Single-Hub.
- boot init/booter+infra: подгон под новые BILLING_HUB_MODE-зависимости и init-порядок.
- desktop ConnectionAgreementStepper: FSD-разгрузка ConnectionAgreementPage — FormStep удалён, выделены DomainStep + FinancialParamsStep, UnionMembershipStep/AgreementStep/ApprovalWaitingStep/InstallationStep/DomainValidationStep сокращены до канона BaseCard/BaseButton/BaseInput.
- desktop entities/Wallet: новый composable useCooperativeMainWallet — единая точка для w.wal.bill COOPERATIVE.
- desktop widgets/ConnectionDashboard: новый MembershipWallet поверх useCooperativeMainWallet, AxonWallet/DomainCard/SubscriptionsCard перевод на канон.
- desktop pages/Union: новый CooperativeDetail; ListOfCooperatives рефакторинг.
- desktop features/Billing/ConvertToBillingDialog: правки под убранный auto-setup на onboarding.
- удалён мёртвый CooperativeBillingPanel widget + CooperativeDataForm feature (заменены каноном).
fix gitgnore
Typecheck / desktop (pull_request) Successful in 27m39s
Typecheck / controller (pull_request) Successful in 13m14s
e69ba70909
Some checks are pending
Typecheck / desktop (pull_request) Successful in 27m39s
Typecheck / controller (pull_request) Successful in 13m14s
Checking for merge conflicts…
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin provider-billing:provider-billing
git checkout provider-billing
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: C9S/mono#129