Appearance
billing-service Agent Tasks
Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.
Dispatch type: per-service
Dispatch ID: SVC-016
Current implementation: native foundation scaffolded in services/billing-service.
Owns: plans, prices, subscriptions, invoices, billing-cycle state, commercial entitlements.
Must read:
- legacy AZ package/subscription/payment modules:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/wallet/az-credit.service.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/wallet/az-credit-business.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/wallet/az-credit.schemas.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/wallet/az-credit.controller.ts. - wallet/payment rows in
docs/migration/legacy-database-inventory.md. - monetization broker handoff:
docs/agents/service-tasks/monetization-broker-transport.md. - shared event contracts in
contracts/events/README.md.
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/wallet/az-credit*.ts- AZ package/subscription Prisma rows in
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma.
Writable files:
services/billing-service/**contracts/openapi/services/billing-service.yamldocs/api/billing-service.md
Orchestrator-owned files:
- payment provider webhook contracts, wallet ledger grants, usage entitlement consumers, gateway route cutovers, and shared deploy manifests unless delegated.
Completed foundation:
- Audited legacy
AzCreditPlan,AzUserSubscription, andAzSubscriptionRequestownership and kept provider webhooks plus wallet ledger outside billing. - Added plan bundle APIs, monthly prices, plan entitlements, subscription plus invoice creation, payment-outcome activation, cancellation, and entitlement snapshots.
- Added
payment.order.paid.v1consumer adapter. It requiresmetadata.subscriptionId, reuses the idempotent payment-activation path, and emitsbilling.subscription.activated.v1plusbilling.entitlements.updated.v1outbox rows only on first delivery. - Exposed
POST /v1/events/payment-order-paidfor the internal payment-service HTTP fanout rehearsal path. Public wallet/subscription routes remain legacy-proxied. - The payment HTTP event receiver requires
X-Internal-Service: payment-serviceplus the sharedX-Internal-Token; it rejects missing or invalid credentials before decoding or activating a subscription. Billing's HTTP outbox publisher similarly sendsX-Internal-Service: billing-serviceand the shared token to usage and wallet, while missing production token configuration fails closed. - Added durable billing event outbox dispatch for
billing.entitlements.updated.v1events to usage-service/v1/events/billing-entitlements-updatedandbilling.subscription.activated.v1events to wallet-service/v1/events/billing-subscription-activated, including in-memory/Postgres pending rows, retry/dead-letter status, andBILLING_EVENT_OUTBOX_*runtime knobs. - Added opt-in NATS JetStream publisher mode for the billing outbox. With
MONETIZATION_EVENT_TRANSPORT=nats, billing publishesbilling.entitlements.updated.v1tomonetization.billing.entitlements.updated.v1andbilling.subscription.activated.v1tomonetization.billing.subscription.activated.v1; HTTP remains the default fallback until subscriber and route proof are complete. - Added zero-price activation fanout for seeded
FREE_TRIAL: idempotent subscription creation marks the subscription active and invoice paid, stores exactly onebilling.entitlements.updated.v1outbox row for usage-service, and does not emitbilling.subscription.activated.v1without payment evidence. - Added non-mutating renewal policy preview at
POST /v1/subscriptions/{subscriptionId}/renewal-preview. It computes the next full-month renewal period, amount, entitlement snapshot, and explicit no-proration policy without creating invoices, billing events, wallet ledger entries, or usage counters. - Added service-local renewal mutation at
POST /v1/subscriptions/{subscriptionId}/renew. It creates idempotent full-period renewal invoices withprorationBehavior=NONE, stores invoice-covered period bounds, leaves paid renewals pending payment-service activation before any usage/wallet outbox events, and advances zero-price renewals with only a usage entitlement event. - Added optional best-effort audit-service producer projection for billing state events.
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URLenables posting to/v1/audit-eventswithsourceService=billing-service,sourceEventId=<billing event id>,category=DOMAIN,severity=INFO,outcome=SUCCESS,metadataSchemaVersion=1,retentionPolicy=billing-events, andcreatedAt -> occurredAt. This producer is separate from the usage/wallet outbox so audit-service failures do not retry downstream entitlement or wallet delivery. - Added
SeedDefaultPlans(ctx)and nativePOST /v1/plans/seed-defaultsfor deterministic, idempotent seeding of legacy fixed plan constants:FREE_TRIAL,AZ_STARTER,AZ_PRO,AZ_CENTER, andAZ_SCHOOL. Entitlements map the legacy commercial limits only: monthly/hourly/weekly credits, monthly tokens, questions, storage bytes, OCR pages, batch import pages, and concurrent jobs. LegacyAZ_CREDIT_ENTERPRISE_PLAN_CODE = "AZ_ENTERPRISE"is returned ascustomPlanMappingswithbillingModel=custom_contract,fixedPrice=false, andfixedEntitlements=false; native seeding does not invent hidden enterprise limits or create an active fixed enterprise plan row. - Added service-owned Postgres migration, memory store, pgx/Postgres store, OpenAPI contract mirror, Dockerfile, README, focused HTTP tests, and an opt-in Postgres repository integration test.
- Added an explicitly confirmed disposable-Postgres runner for the focused subscription replay and missing-outbox payment replay integration tests. It publishes Postgres only on a dynamic loopback port, waits with
pg_isready, unsets the runtimeDATABASE_URL, supplies its own test-only DSN, and removes the container through an exit trap.
Next tasks:
- Broker publisher mode is available but not default. Next monetization work is usage/wallet subscriber proof, replay/dead-letter tooling, live broker smoke, gateway rehearsal, and rollback evidence before public billing route promotion.
- Consume payment review events when the payment-service review contract is finalized.
- Wallet subscription-credit grants are coordinated through wallet-service via
billing.subscription.activated.v1; next work is proration expansion and live route proof, not first-activation or renewal invoice scaffolding. - Add gateway route rehearsal only after event/adapter parity evidence exists.
Acceptance:
- Billing does not ingest provider webhooks directly except via payment events.
- Billing does not own wallet ledger or AZ Credit balance.
- Payment-event activation is replay-safe by source payment event id.
- HTTP event ingestion is native-only and does not promote public routes.
- Plan changes are idempotent and auditable.
- Legacy plan constants are seeded or explicitly mapped for
FREE_TRIAL,AZ_STARTER,AZ_PRO,AZ_CENTER,AZ_SCHOOL, and custom/enterprise plans before subscription route promotion. - Invoice/subscription lifecycle parity covers create, activate, cancel, renewal/proration policy, and entitlement snapshots before public billing routes move native.
- Entitlement events to usage-service and subscription-credit grants to wallet-service are replay-safe and covered by event-delivery tests before plan/subscription cutover.
- Zero-price plan activation is replay-safe: usage receives the entitlement snapshot once, and wallet-service receives no subscription-credit grant unless a payment-backed
billing.subscription.activated.v1is emitted.
Verification:
GOTOOLCHAIN=go1.25.11 go test ./services/billing-service/...BILLING_SUBSCRIPTION_REPLAY_POSTGRES_CONFIRM=disposable-postgres ./scripts/test/billing-subscription-replay-postgres-isolated.shmake test-monetization-event-chain- Migration mapping updated before scaffold cutover.
- OpenAPI parse/diff, Compose config, Helm render/lint, K8s migration and observability coverage after scaffold.
Disposable-runner proof is bounded to billing migrations and the two focused Postgres repository replay tests. It does not use a configured/shared database and does not prove billing runtime startup, provider events, downstream usage/wallet delivery, broker behavior, gateway promotion, or production data.