feat(billing): cancel org subscription on identity.org.deleted (FFRNT-174) - #700
Merged
Merged
Conversation
…-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
Contributor
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📋 Description
billing-service now reacts to
identity.org.deletedby 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
🧪 Testing
Test Instructions
services/billing-service:tsc --noEmitclean; 2 suites / 12 tests pass (6 new handler tests + existing subscription-service suite).🔧 Implementation Details
Changes Made
src/kafka/org-deleted.handler.ts—handleOrgDeleted(event, deps). The org→subscription link is indirect (billing stores noorg_idon subscriptions), so it resolvesorganization → 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).src/kafka/org-deleted.consumer.ts—startOrgDeletedConsumerwires a dedicatedTypedConsumergroup (<group>-org-deleted) onidentity.org.deleted, dead-lettering handler failures via the shared producer. Wired insrc/index.tsand disconnected on shutdown.src/services/subscription.service.ts— addedcancelImmediately(stripeSubscriptionId)(stripe.subscriptions.cancel) for the hard cascade, alongside the existing period-endcancel().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 existingSubscriptionService, theTypedConsumer+ DLQ-producer wiring pattern fromsrc/kafka/consumer.ts, andidentityOrgDeletedSchemaV1(@fuzefront/shared/kafka).Code Quality
📝 Additional Notes
identity.org.deletedis 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. Thehardcascade is wired but dormant until a hard-delete path emits it.Future Work
identity.org.deleted(no portal teardown function exists yet — its own slice).identity.membership.*reactions (+ emit sites);user.updated/deletedemit sites.Generated by Claude Code