Skip to content

feat(billing): cancel org subscription on identity.org.deleted (FFRNT-174) - #700

Merged
izzywdev merged 1 commit into
masterfrom
claude/microservice-event-architecture-qk0zzt
Aug 26, 2026
Merged

feat(billing): cancel org subscription on identity.org.deleted (FFRNT-174)#700
izzywdev merged 1 commit into
masterfrom
claude/microservice-event-architecture-qk0zzt

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

📋 Description

billing-service now reacts to identity.org.deleted by canceling the deleted org's Stripe subscription — the first cross-service org-lifecycle slice of FFRNT-174, mirroring the provisioning-service consumers already merged (#599, #667, #681). The billing service already runs Kafka consumers; this adds a dedicated consumer group for the org-deleted reaction.

🔄 Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

🧪 Testing

  • Unit tests

Test Instructions

cd services/billing-service && npx tsc --noEmit && npx jest tests/kafka tests/services/subscription.service.test.ts
  • services/billing-service: tsc --noEmit clean; 2 suites / 12 tests pass (6 new handler tests + existing subscription-service suite).

🔧 Implementation Details

Changes Made

  • Backend Changes (billing-service):
    • New src/kafka/org-deleted.handler.tshandleOrgDeleted(event, deps). The org→subscription link is indirect (billing stores no org_id on subscriptions), so it resolves organization → billing.customers (entity_type='organization') → subscription, then cancels:
      • cascade: 'soft' (the default org DELETE) → cancel at period end (reversible).
      • cascade: 'hard' → cancel immediately (org is being purged).
      • Best-effort + idempotent: no billing customer / no subscription / already-canceled subscription → no-op (safe on redelivery).
    • New src/kafka/org-deleted.consumer.tsstartOrgDeletedConsumer wires a dedicated TypedConsumer group (<group>-org-deleted) on identity.org.deleted, dead-lettering handler failures via the shared producer. Wired in src/index.ts and disconnected on shutdown.
    • src/services/subscription.service.ts — added cancelImmediately(stripeSubscriptionId) (stripe.subscriptions.cancel) for the hard cascade, alongside the existing period-end cancel().
    • tests/kafka/org-deleted.handler.test.ts — soft/hard cascade, the three no-op paths (no customer, no subscription, already canceled), and failure propagation so the DLQ path fires.

Reused

CustomerRepository.findByEntity + SubscriptionRepository.findByCustomer (src/repositories/*), the existing SubscriptionService, the TypedConsumer + DLQ-producer wiring pattern from src/kafka/consumer.ts, and identityOrgDeletedSchemaV1 (@fuzefront/shared/kafka).

Code Quality

  • Self-review of code completed
  • Code is commented, particularly in hard-to-understand areas
  • No debugging statements left in code

📝 Additional Notes

identity.org.deleted is already emitted by security-service (org soft-delete) and already provisioned as a topic, so this consumer is live end-to-end for the soft-delete path. The hard cascade is wired but dormant until a hard-delete path emits it.

Future Work

  • Portal teardown on identity.org.deleted (no portal teardown function exists yet — its own slice).
  • identity.membership.* reactions (+ emit sites); user.updated/deleted emit sites.

Generated by Claude Code

…-174)

billing-service now reacts to identity.org.deleted by canceling the
deleted org's Stripe subscription — the cross-service org-lifecycle slice
of FFRNT-174, mirroring the provisioning-service consumers.

- new org-deleted.handler.ts: resolve organization -> billing.customers
  (entity_type='organization') -> subscription, then cancel. cascade
  'soft' cancels at period end (reversible), 'hard' cancels immediately.
  Best-effort + idempotent: no customer / no subscription / already
  canceled -> no-op.
- new org-deleted.consumer.ts: a dedicated TypedConsumer group
  (<group>-org-deleted) on identity.org.deleted, DLQ via the shared
  producer; wired + disconnected on shutdown in index.ts.
- SubscriptionService.cancelImmediately() for the hard cascade
  (stripe.subscriptions.cancel), alongside the existing soft cancel().
- tests: soft/hard cascade, no-customer / no-subscription / already-
  canceled no-ops, and failure propagation to the DLQ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxwMXu8tusGrFcZe6iC9j8
@izzywdev izzywdev added the auto-merge Enable squash auto-merge once CI passes label Aug 17, 2026 — with Claude
@github-actions
github-actions Bot enabled auto-merge (squash) August 17, 2026 15:13
@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@izzywdev
izzywdev merged commit 85b1e99 into master Aug 26, 2026
68 of 72 checks passed
@izzywdev
izzywdev deleted the claude/microservice-event-architecture-qk0zzt branch August 26, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge Enable squash auto-merge once CI passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants