Skip to content

Handoff: SVC-016-renewal-mutation-no-proration billing renewal mutation without proration

Scope

  • Service/area: billing-service.
  • Task pack: docs/agents/service-tasks/billing-service.md
  • Goal: accept the bounded SVC-016 slice that adds a service-local no-proration renewal mutation after plan seeding, payment activation, entitlement outbox, and renewal-preview foundations.
  • Non-goals: public/default route promotion, provider webhook ingestion, wallet ledger writes, usage counter ownership, prorated upgrades/downgrades, live gateway/browser proof, or changes to legacy source under /Users/velikho/Desktop/WORKING/HOCTAPAZ/.

Docs Read

  • go-platform/docs/agents/service-agent-execution-queue.json
  • go-platform/docs/agents/service-agent-closeout-ledger.json
  • go-platform/docs/agents/service-tasks/billing-service.md
  • go-platform/docs/agents/service-tasks/payment-service.md
  • go-platform/docs/agents/service-tasks/wallet-service.md
  • go-platform/docs/agents/service-tasks/usage-service.md
  • go-platform/docs/agents/service-tasks/monetization-broker-transport.md
  • go-platform/contracts/events/README.md
  • go-platform/docs/api/billing-service.md
  • go-platform/services/billing-service/README.md

Legacy Evidence Read

  • /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
  • Legacy source was read-only evidence only; no legacy files were changed.

Files Changed

  • go-platform/services/billing-service/internal/domain/billing.go
  • go-platform/services/billing-service/internal/usecase/billing.go
  • go-platform/services/billing-service/internal/usecase/renewal_preview_test.go
  • go-platform/services/billing-service/internal/http/server.go
  • go-platform/services/billing-service/internal/http/server_test.go
  • go-platform/services/billing-service/internal/repository/memory.go
  • go-platform/services/billing-service/internal/repository/postgres.go
  • go-platform/services/billing-service/internal/repository/postgres_integration_test.go
  • go-platform/services/billing-service/migrations/000003_invoice_periods.sql
  • go-platform/services/billing-service/api/openapi.yaml
  • go-platform/contracts/openapi/services/billing-service.yaml
  • go-platform/services/billing-service/README.md
  • go-platform/docs/api/billing-service.md
  • go-platform/docs/agents/service-tasks/billing-service.md
  • go-platform/docs/agents/handoffs/SVC-016-renewal-mutation-no-proration.md
  • go-platform/docs/agents/service-agent-closeout-ledger.json
  • go-platform/docs/agents/service-agent-closeout-ledger.md

Behavior Implemented

  • Added POST /v1/subscriptions/{subscriptionId}/renew as an idempotent, service-local full-period renewal mutation.
  • Kept prorationBehavior=NONE as the only accepted policy.
  • Added nullable invoice period bounds (periodStartedAt, periodEndsAt) and Postgres migration 000003_invoice_periods.sql so renewal invoices carry their intended service period.
  • Paid renewals create an OPEN renewal invoice and leave the subscription's current period unchanged until payment-service sends payment evidence.
  • Payment activation now honors invoice period bounds before falling back to existing subscription periods, so paid renewal activation can advance to the renewal period.
  • Zero-price renewals create a PAID invoice, advance the subscription period immediately, and emit only billing.entitlements.updated.v1 for usage-service.
  • Renewal mutation does not ingest provider webhooks, does not write wallet ledger rows, and does not emit billing.subscription.activated.v1 unless a payment-backed activation path runs.

Tests And Verification

  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/billing-service/internal/usecase ./services/billing-service/internal/http ./services/billing-service/internal/repository -run 'TestBillingRenew|TestBillingRenewal|TestBillingSubscriptionPaymentReplayAndEntitlements|TestHandlePaymentOrderPaidActivatesSubscriptionAndEmitsEntitlements|TestCreateFreeTrial' -count=1 -v
  • Result: passed.
  • Evidence: focused tests covered paid renewal invoice creation, idempotent replay, no-proration policy, zero-price renewal period advancement, usage-only entitlement event, no pre-payment usage/wallet outbox emission for paid renewals, and later payment activation using invoice period bounds.
  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/billing-service/... -count=1
  • Result: passed.
  • Evidence: billing-service auditsink, eventfanout, HTTP, repository, and usecase packages passed.
  • Command: ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path); puts "ok #{path}" }' services/billing-service/api/openapi.yaml contracts/openapi/services/billing-service.yaml
  • Result: passed.
  • Evidence: service and contract OpenAPI files parsed successfully.
  • Command: cmp -s services/billing-service/api/openapi.yaml contracts/openapi/services/billing-service.yaml
  • Result: passed.
  • Evidence: service OpenAPI and contract mirror are byte-identical.
  • Command: make test-monetization-event-chain
  • Result: passed.
  • Evidence: billing/payment/wallet/usage monetization event-chain coverage passed.
  • Command: make test-monetization-broker-transport
  • Result: passed.
  • Evidence: broker transport coverage, self-test smoke, eventfanout packages, and monetization outbox tests passed.

Route State

  • Legacy route: public /api/wallet*, /api/admin/wallet*, subscription pages, provider hooks, and /wallet?tab=billing remain legacy-proxied by default.
  • Native route: billing-service exposes service-local /v1/subscriptions/{subscriptionId}/renew for renewal rehearsal and internal callers.
  • Gateway state: no default gateway route was changed and no public billing route was promoted.
  • Rollback: leave deploy/gateway/routes.json as the active default route table; disable billing-service callers or avoid routing public subscription traffic to /v1/subscriptions/{subscriptionId}/renew.

Data Notes

  • Tables touched: billing_invoices gains nullable period_started_at and period_ends_at.
  • Migration added: services/billing-service/migrations/000003_invoice_periods.sql.
  • Validation report: not applicable because no live data was migrated or backfilled.

Risks / Blockers

  • Prorated upgrades, downgrades, credit carry-over, and partial-period billing remain unsupported by design.
  • Paid renewal wallet credits still require payment-service paid events and wallet-service consumption of billing.subscription.activated.v1.
  • No live gateway/browser proof or public route rollback proof is included.
  • Optional Postgres renewal proof was not run because BILLING_SERVICE_POSTGRES_TEST_DATABASE_URL was not set in this environment.

Next Step

  • Add live billing route rehearsal and rollback evidence after payment-service, wallet-service, usage-service, and gateway route proofs are ready; keep public subscription pages legacy-proxied until then.

Go-platform documentation is generated from repository Markdown.