Skip to content

feat(billing): billing-service + @fuzefront/billing-ui (re-land of orphaned #81) - #88

Merged
izzywdev merged 26 commits into
masterfrom
feat/billing-ui
Jun 22, 2026
Merged

feat(billing): billing-service + @fuzefront/billing-ui (re-land of orphaned #81)#88
izzywdev merged 26 commits into
masterfrom
feat/billing-ui

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

Why a new PR

#81 reported MERGED but its squash commit (a3084f3) was orphaned — it was admin-merged while the branch was behind #87 (which auto-merged moments earlier), so master's ref never advanced to it and billing never landed. This re-lands the same work, now up-to-date with master.

Contents

  • services/billing-service (Stripe client, subscription/metering/plan/customer/permit services + tests)
  • @fuzefront/billing-ui (design-system-first UI package) + @fuzefront/billing-client
  • Registered billing-client + packages/billing-ui in root workspaces
  • Migration renumbered 010 → 011 (collision with identity's 010_create_api_tokens_table already on master)
  • Lockfiles regenerated on Linux (win32 rollup stays optional)

Verification

  • In-repo packages resolve from source (workspace-deps gate) green
  • ✅ Up-to-date with master (no orphaning this time)
  • ℹ️ backend-tests is pre-existing-red on master itself (not a billing regression)

🤖 Generated with Claude Code

AppHub Developer and others added 26 commits June 21, 2026 23:29
Add four new TOPICS constants (BILLING_USAGE_RECORDED, BILLING_SUBSCRIPTION_CHANGED,
BILLING_TRIAL_ENDING, BILLING_PAYMENT_FAILED) to shared/src/kafka/types.ts.

Add two new Zod payload schemas:
- billingUsageRecordedSchemaV1 (entityId uuid, entityType enum, meterEventName, quantity
  int positive, occurredAt datetime) — no correlationId, it lives on the FuzeEvent envelope.
- billingSubscriptionChangedSchemaV1 (entityId uuid, entityType enum, planTier, status,
  optional seatQuantity int, stripeSubscriptionId) — mirrors Stripe subscription status.

Wire both schemas into shared/src/kafka/schemas/index.ts barrel.

Add minimal Jest test runner to shared (jest + ts-jest mirroring email-service conventions).
18 new schema-parse tests cover valid parses and invalid rejections (bad uuid, negative
quantity, wrong enum, bad datetime, missing required fields).

TopicName type widens automatically from the updated TOPICS const — no manual changes needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the billing-service microservice skeleton under services/billing-service/.
Includes TDD health + config tests (5/5 passing), multi-stage Dockerfile
(Docker build verified), and lerna.json registration.

Deviations from email-service pattern (documented):
- @fuzefront/shared uses file:../../shared (not registry version) for
  local dev without GitHub token — same as provisioning-service
- Dockerfile uses `cd X && npm install` instead of `npm ci --workspace=X`
  because billing-service is not in root workspaces; `npm ci --workspace`
  fails for services not declared in root package.json workspaces
- Shared built with `npm run build:kafka` (not `npm run build`) to avoid
  socket.io-client / React DOM dep that blocks full-barrel tsc in Docker

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the callback-based server.close in a Promise so later tasks (Kafka/DB
teardown) can sequence async cleanup before process.exit. Addresses T2 review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- src/migrations/001_billing_schema.sql: idempotent DDL for all 5 billing.*
  tables (customers, subscriptions, stripe_events, usage_events, plans)
  with FK, UNIQUE constraints, and pgcrypto extension guard
- src/db.ts: createPool (pg.Pool factory) + runMigrations (reads + executes SQL)
- src/index.ts: call runMigrations at boot, guarded by config.databaseUrl
- tests/db.test.ts: SQL-shape assertions (28 passing, no live DB needed) +
  skipped integration suite for when DATABASE_URL is present

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds four billing hot-path cache columns to both the `users` and
`organizations` tables (public schema):
  - stripe_customer_id   TEXT, nullable
  - billing_plan_tier    TEXT NOT NULL DEFAULT 'free'
  - billing_plan_status  TEXT NOT NULL DEFAULT 'active'
  - trial_ends_at        TIMESTAMPTZ, nullable

Each column addition is guarded with hasColumn so the migration is
idempotent. down() drops all eight columns with matching guards.

Added to both backend/src/migrations/ and backend/security/src/migrations/
(byte-identical) per the dual-location lock-step policy (001–010 identical
across both dirs).

Extended the security migration integration test to assert all 8 billing
columns exist with correct nullability and defaults after the chain runs.
The test skips cleanly when Postgres is unreachable (CI-safe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es, kafka, helm/values, release wiring (incomplete; types pending) [skip ci]
… validation, dockerignore

Resolves the salvage checkpoint's "types pending" item. The billing-service
TypeScript production build now passes (verified via docker build of
services/billing-service/Dockerfile, EXIT=0).

- Route input validation: add validateBody() helper returning a non-union
  ValidationResult so routes read .data/.details without discriminated-union
  narrowing, which does not fire under the service's strict:false tsconfig
  (mirrors the cast workaround in sms-service). Refactor setup-intent,
  subscriptions, credits routes onto it.
- @fuzefront/shared kafka imports already resolve via /dist/kafka subpath
  (classic node resolution can't read the package "exports" map).
- Add root .dockerignore excluding **/node_modules + **/.git so a host
  (Windows) node_modules cannot shadow the in-image install and corrupt the
  build (was producing a truncated typescript binary).
- Add run-tests-in-docker.sh: runs jest in a clean node:18 Linux container
  (local Windows npm install is unreliable here; CI runs jest natively).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CustomerRepository interface gained findByStripeCustomerId (used by the
webhook handlers' reverse lookup). The customer.service unit test's in-memory
fake must implement it to satisfy the interface. Fixes the one failing suite
(9/10 → 10/10; other 9 suites and 51 tests already passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The identity track also adds a 010_* migration (010_api_tokens). Add an
in-file note that final integration must renumber one of the two 010 migrations.
PR body carries the same warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The committed lock referenced @types/jest in the manifest mirror but lacked
the node_modules/@types/jest install entry, so `npm ci` failed with
'Missing: @types/jest from lock file' in CI and Docker builds. Regenerate
the lock so ci/build install deterministically.

Verified in node:18-alpine (mirrors CI):
- billing-service: npm ci OK, tsc --noEmit (src+tests) OK, jest 54 passed/3 skipped
- billing-client: npm ci OK, tsc --noEmit (src+tests) OK, jest 6 passed
- stripe 17.7.0 (bundled types), no live Stripe calls (mocked)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ild, vitest, private publishConfig) [skip ci]
…Manager, UsagePanel, PaymentMethodPanel, barrel [skip ci]
…ocked Stripe, subscription, panels, modal, status) [skip ci]
…nder jsx-runtime); ignore .npm-cache [skip ci]
# Conflicts:
#	deploy/helm/fuzefront/templates/secret.yaml
#	deploy/helm/fuzefront/values.yaml
#	lerna.json
#	package-lock.json
#	shared/dist/index.d.ts
#	shared/dist/index.js
#	shared/dist/kafka/schemas/index.d.ts
#	shared/src/kafka/schemas/index.ts
…ORM on CI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t workspaces

The agent's workspace registration was left unstaged; the merge commit omitted it,
so CI's workspace-deps gate saw @fuzefront/billing-client (a peerDep ^1.0.0) as an
unregistered in-repo package. Register both so it resolves from source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… with identity 010_api_tokens)

#65 (on master) added backend/security migration 010_create_api_tokens_table; billing's
010_add_billing_to_entities collided. Renumbered to 011 to restore a unique ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI autofix: ERR_MODULE_NOT_FOUND crash in shared/dist/kafka

Root cause: shared/dist/kafka/index.js was compiled with "module": "esnext", producing bare ESM re-exports (export * from './types'). Node.js 22 (the runner's actual version despite the node-version: '20.x' pin) loads these via loadESMFromCJS, which enforces strict ESM resolution and cannot find bare ./types — no .js extension. The backend crashed before binding to port 3001, causing the 2-minute health-check loop to time out.

Fix: Changed shared/tsconfig.json from "module": "esnext" to "module": "commonjs" and regenerated all dist files. CommonJS require() resolves bare paths without extensions, eliminating the crash. Backend consumers are all CommonJS; frontend bundlers (Vite, webpack) handle CommonJS packages transparently.

Branch: claude-auto-fix-ci-feat/billing-ui-27966662418 — ready to merge into feat/billing-ui.
(The bot cannot open PRs per repo policy, so the branch is pushed and listed here.)

@izzywdev
izzywdev merged commit 64034d7 into master Jun 22, 2026
14 of 19 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

CI failure root cause + fix (automated)

Failing job: Playwright sign-in flow → Build & start backend step

Error:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../shared/dist/kafka/types'
imported from .../shared/dist/kafka/index.js

Root cause: The committed shared/dist/kafka/ files were built with tsconfig.json ("module": "esnext"), so they contain ESM syntax (export * from './types'). The GitHub Actions runners now execute on Node.js 22+, which has native require()-of-ESM interop. When Node.js 22+ require()s an ES module, it hands off to the ESM resolver — which requires explicit .js extensions for relative imports. './types' fails while './types.js' would succeed.

Fix: The shared package already has tsconfig.kafka.json with "module": "commonjs" exactly for this case. Re-ran npm run build:kafka and committed the resulting CJS output to shared/dist/kafka/.

Branch: claude-auto-fix-ci-feat/billing-ui-27967013753

The branch is pushed and ready to be merged into feat/billing-ui.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants