Epic 13: Биллинг v5.1 — package-based подписки через PowerUp + P0 фиксы #49
Open
claude
wants to merge 23 commits from
feat/epic-13-billing-package-powerup into provider-billing
pull from: feat/epic-13-billing-package-powerup
merge into: C9S:provider-billing
C9S:main
C9S:dev
C9S:marketplace2
C9S:testnet
C9S:fix/598-51-membership-fee-and-type-cleanup
C9S:feat/capital-canon
C9S:feat/598-supplier-registry
C9S:feature/capital-cpp-private-data-templates
C9S:fix/soviet-install-validation
C9S:feat/ku-selforg
C9S:feat/membership-exit
C9S:feat/marketplace-admin-orders
C9S:fix/capital-share-onproject-event
C9S:ci/independent-env-deploy
C9S:fix/registration-pending-display
C9S:coopID
C9S:feat/expense-chassis-mvp
C9S:fix/zip-utf8-flag
C9S:feat/coopid-auth-ui
C9S:feat/coopid-7-signing-bridge
C9S:feat/coopid-12-recovery-account-from-confirm
C9S:feat/coopid-epic-9-metrics
C9S:feat/coopid-epic-6-access-rules-cleanup
C9S:feat/coopid-epic-12
C9S:feat/coopid-epic-11
C9S:feat/coopid-epic-7
C9S:feat/coopid-epic-9
C9S:feat/coopid-epic-6
C9S:feat/marketplace-process-registry
C9S:feat/marketplace-notifications-map
C9S:feat/marketplace-ku-economy
C9S:savanna
C9S:feat/apps-catalog-finish
C9S:feat/coopid-epic-4
C9S:feat/coopid-epic-3
C9S:feat/blagorost-on-chassis
C9S:feat/coopid-epic-1
C9S:feat/marketplace-coop-stock
C9S:feat/marketplace-live-catalog
C9S:feat/coop-invest-blagorost
C9S:feat/ff-release-flow
C9S:fix/marketplace-offer-instance-not-template
C9S:fix/extension-config-lost-update
C9S:feature/C28-28-email-relay
C9S:feat/E16-onsite-sign-gate
C9S:feat/pwa-version-watch
C9S:fix/participants-registry-status-badges
C9S:feat/E16-coop-categories
C9S:fix/sanitize-blockchain-error-toast
C9S:fix/wallet-withdraw-no-phantom-payment
C9S:fix/agenda-program-offer-links
C9S:feat/notification-center-dc-v3
C9S:fix/chatcoop-livekit-room-name
C9S:feat/delete-inactive-participant
C9S:feat/registration-ledger2-suspense
C9S:feat/registration-refund
C9S:feat/registration-payment-dedup
C9S:perf/factory-parallel-fetch
C9S:feat/E16-sklad-rework
C9S:fix/decision-createproject-tsc
C9S:perf/factory-weasyprint-warm-pool
C9S:feat/E16-korzina-zakaz-agregat
C9S:fix/agenda-pending-new-question
C9S:feat/soviet-negative-consensus
C9S:feat/registration-payment-decline
C9S:feat/coop-standards-audit
C9S:parser2
C9S:feat/mkt-return-chairman-cosign
C9S:feat/registration-fsm
C9S:feat/pwa-reliable-update
C9S:parser2-block-time-and-version
C9S:fix/gorozhane-phantom-cleanup
C9S:fix/remove-bank-kpp
C9S:feat/E9-3b-sub-subscribe
C9S:feat/E9-9-approve-moderation
C9S:feat/E10-5-on-chain-watcher
C9S:feat/E10-4-install-pipeline
C9S:feat/E9-3b-ui-publish-package
C9S:feat/E1-tspp-docs-real-text
C9S:feat/E10-3b-dynamic-supergraph
C9S:apps-catalog
C9S:feat/E9-9-3-b-publish-mutation
C9S:feat/sig-v2-export
C9S:feat/E10-1-federation-subgraph
C9S:parser2-epic-4-forks
C9S:feat/decision-authorize-backend
C9S:feat/blagorost-program-expenses-ui
C9S:parser2-epic-6-canonical-storage
C9S:wip/pre-e16-desktop-docs
C9S:feat/E15-min-volume-per-ku
C9S:feat/blagorost-E2-E5-program-expenses
C9S:feat/epic-14-billing-notifications
C9S:blagorost
C9S:feat/E14-explicit-shipment
C9S:feat/marketplace-collective-supply
C9S:feat/blagorost-E1-contract
C9S:feat/C28-21-pg-signed-documents
C9S:provider-billing
C9S:fix/desktop-format-asset-errors
C9S:feat/marketplace-docs
C9S:feat/epic-12-billing-subscriptions
C9S:fix/min-soviet-members-3
C9S:parser2-epic-3-transport
C9S:feat/marketplace2-drop-blocked
C9S:worktree-design-wave1
C9S:feat/notif-mobile-canon
C9S:fix/vue-tsc-desktop
C9S:worktree-marketplace-docs-impl
C9S:feat/chatcoop-hide-matrix-room-id
C9S:feat/epic-8-coop-registry-provider
C9S:parser2-epic-2-idempotency
C9S:feat/chatcoop-secretary-rooms
C9S:feat/remove-l3-blocked
C9S:ci-trial-typecheck-2026-05-23
C9S:fix/controller-revert-transpileonly
C9S:chore/mongo-standalone-local
C9S:chore/ci-unify-release-workflows
C9S:feat/controller-swc-builder
C9S:feat/chatcoop-transcription-memo
C9S:fix/mono-base-ca-certificates
C9S:fix/wallet-withdraw-gateway-timing
C9S:feat/ledger2-burn-blocked-withdraw
C9S:fix/boot-tests-wallet-signagree-v2
C9S:chore/trustees-ownership
C9S:feat/capital-w-cap-gen-cooperative
C9S:chore/re-review-E5-apl-fail-fast
C9S:chore/re-review-E4-fail-fast-tx-hash
C9S:chore/re-review-E3-fix-withdraw-stub
C9S:chore/review-E3-vitrina-fixes
C9S:chore/review-E10-design-fixes
C9S:chore/review-E8-spisanie-fixes
C9S:chore/review-E7-vozvrat-fixes
C9S:chore/review-E6-vydacha-fixes
C9S:chore/review-E5-postavka-fixes
C9S:chore/review-E4-order-fixes
C9S:feat/epic0-onboarding-harness
C9S:chore/marketplace2-sync-dev-and-controller-di
C9S:feat/E11-stol-zakazov-frontend-and-tests
C9S:feat/E11-stol-zakazov-final
C9S:feat/capital-artifacts-access
C9S:fix/floor-asset-display
C9S:feat/epic-9-warehouse-reporting
C9S:feat/598-11-epic-8-writeoff
C9S:feat/E7-garantiynyy-vozvrat
C9S:design
C9S:fix/capital-revert-394-generation-convert-refs
C9S:fix/is-unioned-zod-string-parse
C9S:fix/capital-rename-generation-convert-to-money-invest
C9S:feat/E6-vydacha-payshchiku
C9S:worktree-remove-1081-1082-rename-1080
C9S:feat/init-by-server-from-provider
C9S:feat/E5-postavka-priemka
C9S:feat/E11-techdebt-L12-split-payout
C9S:feat/E4-zakaz
C9S:gh-pages
C9S:feat/E3-vitrina-canon
C9S:feat/E11-S1-ledger2-marketplace-ops
C9S:file-storage
C9S:feat/1-10-marketplace-registration-offer-status
C9S:feat/1-9-marketplace-board-acceptance
C9S:feat/1-8-marketplace-access-matrix
C9S:feat/1-7-marketplace-template-registry
C9S:feat/1-6-marketplace-role-guard
C9S:feat/1-5-marketplace-member-wallet
C9S:feat/1-4-marketplace-onboarding-gate
C9S:feat/1-3-marketplace-access-by-membership
C9S:feat/1-1-install-from-catalog
C9S:ci/lock-tags-only
C9S:onboarding
C9S:reports
C9S:ledger3
C9S:graph-standarts
C9S:feat/ledger-double-entry
C9S:feat/docs-enrichment
C9S:feat/documentation
C9S:feat/marketplace-orders
C9S:feat/interops-extension-system
C9S:cursor/development-environment-setup-2882
C9S:feat/unified-test-pipeline
C9S:fix/security-vulnerabilities
C9S:audio-stream
C9S:project-authorization-on-create
C9S:refactoring
C9S:capital
C9S:element
C9S:ledger
C9S:marketplace
C9S:notifications2
C9S:notifications
C9S:document2
C9S:TSK-900
C9S:TSK-878
C9S:dacom-dark-sun/sprint-4
C9S:uniconstructor/sprint-5
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/epic-13-billing-package-powerup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Зонтичный PR Epic 13 со стороны mono. Stories 13.2 (w.wal.bill USER_SHARED → COOPERATIVE), 13.3 (billing::topupaxon action + o.bil.axn), 13.4/13.5/13.6 (PowerupPlugin 8 guards + calendar+threshold + BillingProviderClient), 13.8 (desktop UI). + adversarial review P0 фиксы
6ccc167584: BlockchainService.powerUp no-swallow + tx_id return, PowerupPlugin mutex, двухфазный flow createPackageInvoice → powerUp → confirmTopupAxon, отключён фиктивный axon_pre_gate, ProviderSubscription расширен полями kind/monthly_quota_rub/packages_current_period_amount, ChairmanDesktop manifest зарегистрировал /:coopname/powerup. SDK regenerated. Связанный provider PR: C9S/provider#25Зеркало existing confirmPayment, но для package-модели Epic 13: POST `/billing/topup-axon-confirmed` с {payment_hash, tx_id}. Используется (a) на coopback'е пайщика-кооператива в PowerupPlugin после успешного on-chain topupaxon (Story 13.5); (b) на coopback'е Воскхода (BILLING_HUB_MODE=true) как defensive-handler, если PowerupPlugin не успел. Идемпотентность — на стороне provider (markPackageTopupReceived).- calendarCronJob: cron('0 0 1 * *', timezone:'UTC') — runCalendarTask. Идемпотентность через config.lastCalendarPackageMonth (YYYY-MM): повтор в том же месяце — no-op. Гарантирует «свежий пакет в начале месяца», даже если threshold-триггер ещё не сработал. - runCalendarTask → executePackageTopup('calendar', nowIso). - runTask (per-minute): при needPowerUp вместо прямого powerUp() теперь зовёт executePackageTopup('threshold', nowIso) — единая точка для всех guards. - executePackageTopup: единая обработка обоих триггеров с checkRunawayGuards + recordTopup (fresh-snapshot commit lastTopupAt + daily counters + lastCalendarPackageMonth для calendar-trigger'а). При MonthlyPackageQuotaExceeded от provider'а → tripCircuitBreaker(monthKey). - onModuleDestroy: остановка calendarCronJob. TODO (по мере раскатки): подключить BillingProviderClient.confirmTopupAxon (через provider createPackageInvoice → on-chain billing::topupaxon → confirm).Epic 13 v5.1. Coopback Воскхода (BILLING_HUB_MODE) узнаёт о on-chain конвертации членский→AXON РЕАКТИВНО из блокчейна, без push с горячего пути PowerupPlugin — кооператив-спица подписывает converttoaxn сам (coopname@active) даже если провайдер лежит. - parser: добавлен контракт `billing` в subscribedContracts (иначе action не попадёт в Redis-шину парсера → не дойдёт до EventEmitter2). - BillingConversionListener: @OnEvent('action::billing::converttoaxn') парсит coopname/amount/payment_hash + tx_id и зовёт BillingProviderClient.confirmTopupAxon. Парсер-агностичен (легаси parser и parser2 сходятся на шине action::). Ошибки не пробрасываются — автономность коопа важнее, провайдер до-сверится реконсиляцией. - BillingProviderClient.confirmTopupAxon: добавлены coopname + amount_rub (провайдер реактивно заводит package-invoice и списывает квоту); правлен устаревший docstring topupaxon → converttoaxn. - BlockchainConsumerService: на хабе billing-actions всех коопов-спиц проходят coopname-фильтр (иначе converttoaxn партнёров отбрасывался). - billing.module: регистрация листенера. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>@@ -34,1 +35,4 @@lastDailyReplenishmentDate: '',// ===== Epic 13 v5.1 — 8 слоёв защиты от runaway =====// 1) Cooldown между двумя докупками — минимум 5 минут.у нас вообще покупок нет - мы кооператив - есть аренда вычислительных ресурсов.
@@ -115,0 +228,4 @@.default(defaultConfig.lastTopupAt).describe(describeField({ label: 'Время последней докупки (ISO)', visible: false })),subscriptionId: z.number()visible - false должен быть! Иначе редактировать смогут и ломать. Не надо. Просто по-умолчанию 0 и все.
@@ -115,0 +253,4 @@.number().min(0).default(defaultConfig.monthRubSpent).describe(describeField({ label: 'RUB потрачено за месяц', visible: false })),Ну и RUB надо убрать отсюда напрямую и взять из config символ валюты на случай смены.
@@ -0,0 +6,4 @@import { BillingProviderClient } from './billing-provider.client';/*** Epic 13 v5.1 — реактивный мост on-chain → провайдер для пакетного PowerUp.И запомни, что когда указываешь Эпики - ты должен указывать в рамках какого проекта ты работаешь. Конкретно этот эпик 13 - это проект провайдера. А то у меня есть и другие эпики из других проектов и хрен разберешься уже зачем вообще тогда их указывать было.
@@ -92,0 +110,4 @@coopname: string;amountRub: number;}): Promise<void> {const url = `${this.baseUrl}/billing/topup-axon-confirmed`;А че на клиента не перешел то здесь? КОторый генерируется провайдером.
@@ -269,3 +268,1 @@await this.transact(actions);} catch (error) {this.logger.info('Предупреждение при выполнении транзакции powerup:', String(error));// Epic 13 v5.1: пропускаем исключения наружу — PowerupPlugin полагается нааналогично про эпики
@@ -273,0 +282,4 @@return txId as string;}public async packagePowerUp(Чет не понятно здесь. Ты этот поверап делаешь на сумму 1500 руб? Т.е. конвертируешь 1500 руб это понятно, но поверап то должен быть на сумму 5 AXON а не на всю сумму. Как ты делаешь сейчас??
@@ -0,0 +30,4 @@)BaseInput(v-model.number="form.cooldownMinutes"label="Cooldown между докупками (мин)"НЕТ ДОКУПОК! И НЕТ ПОКУПОК! ЕСТЬ АРЕНДА ВЫЧИСЛИТЕЛЬНЫХ РЕСУРСОВ.
Спасибо за ревью, отвечаю по пунктам (Epic 13 — проект «Облачный провайдер»). По коду внесу отдельным фикс-коммитом; здесь — ответы по сути и вопрос по №8.
№2 (сумма PowerUp). Суммы разделены, powerup НЕ на всю сумму.
packagePowerUp(coopname, rubAmount, axonQuantity, hash)шлёт две action атомарно:billing::converttoaxnнаrubAmountиeosio::powerupнаaxonQuantity. ЗдесьaxonQuantity = dailyPackageSizeAXON (напр. 5 AXON) — это и есть размер дневной квоты;rubAmount = dailyPackageSize × 10₽ (10 ₽ = 1 AXON) — ровно столько членского, сколько нужно под эти AXON. А 1500 ₽ — этоmonthly_quota_rub/monthlyRubCap, месячный потолок (runaway-guard), не разовая аренда. Т.е. powerup идёт на ~5 AXON, а конвертируется ~50 ₽, не 1500.№8 (клиент провайдера). Согласен — заменю ручной
axiosвbilling-provider.client.tsна генерируемый провайдером клиент (типобезопасность контракта). Уточни: использовать публикуемый пакет клиента провайдера (components/client/@coopenomics/provider-client)? Он покрывает billing-эндпоинты (/billing/topup-axon-confirmed,/subscriptions/billing-summary)? Если да — перейду на него.№3, №4 (терминология). Принято: «аренда вычислительных ресурсов», не «покупка/докупка». Поправлю user-facing тексты (desktop
SettingsForm+describeFieldlabels/notes) и комментарии. Технические идентификаторы (on-chainconverttoaxn, endpointtopup-axon-confirmed, имена методов) оставлю — это контракт с провайдером/чейном, не видно пользователю.№5 (visible). Поле настройки →
visible: false+ default 0 (чтобы оператор не редактировал и не ломал).№6 (RUB из config). Уберу хардкод
RUB, возьму символ валюты из config (root_govern_symbol) — на случай смены символа.№1, №7 (эпики + проект). Понял и запомнил: при ссылке на эпик всегда указываю проект, т.к. номера пересекаются между проектами. Поправлю комментарии «Epic 13» → «Epic 13 (проект «Облачный провайдер»)».
Фиксы внесены коммитом
b51e9e0b435:subscriptionId→visible:false, default 0;RUB→ символ из config (root_govern_symbol) в labels (module + SettingsForm);billing-provider.client— пока axios; добавлен TODO+обоснование (опубликованный@coopenomics/provider-clientalpha 2025.11 не содержит billing-эндпоинтов Epic 13; миграция = regen+publish клиента из актуального swagger, отдельная задача). Если есть свежесобранный клиент — скажи, перейду.axonQuantity(dailyPackageSize AXON), 1500 = месячный потолок.Epic 14: типобезопасные workflow в @coopenomics/notifications для напоминаний пайщикам о предстоящей оплате и смене статуса подписки. Шаги email+in-app+push, payload по zod, шаблоны на {{payload.*}} (без Handlebars-ветвлений). Регистрация в allWorkflows. Триггер — провайдер через coopback (Вариант А), Story 14.4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Epic 14: входящий REST-канал /billing-notifications (ServerSecretGuard по общему SERVER_SECRET, симметрично провайдеру). DTO {kind,coopname,payload}; bridge маппит стабильный kind → Workflows.X.id и шлёт через NotificationSenderService (он сам резолвит subscriber_id/email по coopname). coopName в payload обогащается из getDisplayName. Ошибки Novu не пробрасываются — провайдеру важен факт приёма. Зависит от пересборки @coopenomics/notifications (Story 14.1) — Workflows.* id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Правки по итогам ревью потока оплат (2026-06-11), коммиты 6047b… (контракт) +
9463ab7(coopback):paymentsв контракте billing — повторpayment_hashвpay/converttoaxnотклоняется. Зависший парсер / упавший backend больше не приводят к повторным списаниям средств. RAM — скользящее окно (TTL 90д, gc ≤8/транзакция).billing::payавторизует_provider(Восход): рекуррентные списания подписывает оператор своим ключом; ключи спиц на хабе не нужны.summary → POST /billing/invoice → pay → POST /billing/payment-confirmed(раньше confirm бил в выпиленный endpoint, а invoice никто не создавал — подписки не продлялись). Ошибка anti-replay = «уже оплачено» → доносим подтверждение без повторного списания.action::billing::pay(идемпотентно у провайдера).⚠️ Контракт требует пересборки wasm/ABI. Остаётся архитектурный блокер e2e: scope-разрыв
w.wal.bill(конвертация на хабе кладёт в bill@voskhod, расход смотрит bill@спица) — схема и целевая модель v5.2 в blago проект 43, требование d9 «Поток оплаты подписок».View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.