Skip to content

Comments

feat: add BillingCacheService with 1-hour TTL for team subscription data#23934

Merged
ThyMinimalDev merged 10 commits intomainfrom
devin/billing-cache-service-1758267128
Sep 24, 2025
Merged

feat: add BillingCacheService with 1-hour TTL for team subscription data#23934
ThyMinimalDev merged 10 commits intomainfrom
devin/billing-cache-service-1758267128

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Sep 19, 2025

What does this PR do?

This PR implements Redis-based caching for the BillingService using a proxy pattern, following reviewer feedback to remove the BillingCacheService abstraction. The implementation adds a 1-hour cache for billing subscription checks with proper cache invalidation when webhooks are received or cancelTeamSubscription is called.

Key Changes:

  • ✅ Created IBillingService interface for type safety and dependency injection
  • ✅ Implemented BillingServiceCachingProxy that directly uses RedisService
  • ✅ Added cache invalidation for all Stripe webhook handlers and subscription cancellation
  • ✅ Removed the unnecessary BillingCacheService abstraction layer
  • ✅ Updated module configuration to use proxy pattern with DI
  • ✅ Improved type safety with proper Prisma types in BillingData

Architecture: The proxy intercepts getBillingData() calls, checks Redis cache first, falls back to the underlying service on cache miss, and stores the result with 1-hour TTL. Cache is invalidated on billing-related events.

Visual Demo (For contributors especially)

N/A - This is an internal caching optimization with no user-facing changes.

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - Internal caching optimization, no API changes.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Environment Setup:

  • Redis must be configured and running
  • Stripe webhook endpoints should be configured

Test Scenarios:

  1. Cache Hit/Miss: Call GET /v2/billing/{teamId}/check multiple times - first call should query database, subsequent calls should return cached data
  2. Cache Invalidation: Trigger Stripe webhooks (subscription events, payment events) and verify cache is cleared
  3. Subscription Cancellation: Call cancelTeamSubscription and verify cache is invalidated
  4. Cache Expiry: Wait 1 hour and verify cache expires naturally

Expected Behavior:

  • Billing data should be cached for 1 hour after first retrieval
  • Cache should be immediately invalidated on relevant webhook events
  • No functional changes to billing behavior, only performance improvement

Checklist

⚠️ Critical Review Areas:

  • Cache invalidation logic - Verify all webhook handlers properly clear cache for correct team IDs
  • Interface implementation - Ensure proxy correctly implements all IBillingService methods
  • Redis error handling - Confirm service degrades gracefully if Redis is unavailable
  • Type safety - Validate BillingData type matches actual service return types
  • Cache key uniqueness - Verify cache keys are properly scoped by team ID

Link to Devin run: https://app.devin.ai/sessions/adaf284159974bde9c4695f62e310f87
Requested by: morgan@cal.com

This refactor addresses Keith's feedback to simplify the architecture by removing the intermediate BillingCacheService and having the proxy interact directly with Redis.

- Create BillingCacheService following CalendarsCacheService pattern
- Use teamId-based cache keys with 1-hour TTL (3,600,000 ms)
- Integrate caching into getBillingData method in BillingService
- Add cache invalidation to all webhook handlers:
  - handleStripeSubscriptionDeleted
  - handleStripePaymentSuccess
  - handleStripePaymentFailed
  - handleStripePaymentPastDue
  - handleStripeCheckoutEvents
- Add cache invalidation to cancelTeamSubscription method
- Add RedisModule import to billing module
- Add BillingCacheService to billing module providers
- Add findTeamByPlatformBillingId method to OrganizationsRepository for cache invalidation

Co-Authored-By: morgan@cal.com <morgan@cal.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

- Extract IBillingService interface with all public methods
- Create BillingServiceCachingProxy that implements caching logic
- Remove all caching logic from original BillingService
- Simplify cache invalidation using billing.id = team.id
- Update module to use proxy with proper dependency injection
- Update controller to inject proxy interface
- Remove unused BillingService import from controller

This follows the proxy pattern requested in PR feedback, separating
caching concerns from core billing logic for better maintainability.

Co-Authored-By: morgan@cal.com <morgan@cal.com>
@vercel
Copy link

vercel bot commented Sep 19, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Sep 23, 2025 7:50am
cal-eu Ignored Ignored Sep 23, 2025 7:50am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

@ThyMinimalDev ThyMinimalDev marked this pull request as ready for review September 19, 2025 08:52
@ThyMinimalDev ThyMinimalDev requested a review from a team September 19, 2025 08:52
@ThyMinimalDev ThyMinimalDev requested a review from a team as a code owner September 19, 2025 08:52
@graphite-app graphite-app bot requested a review from a team September 19, 2025 08:52
@dosubot dosubot bot added billing area: billing, stripe, payments, paypal, get paid ✨ feature New feature or request labels Sep 19, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Sep 19, 2025

E2E results are ready!

@ThyMinimalDev ThyMinimalDev force-pushed the devin/billing-cache-service-1758267128 branch from 2eff232 to a93deea Compare September 19, 2025 12:57
devin-ai-integration bot and others added 2 commits September 22, 2025 10:45
- Remove BillingCacheService abstraction as suggested by @keithwillcode
- Move cache methods directly into proxy as private methods
- Update proxy to inject RedisService directly
- Move BillingData type to interface for better type safety
- Remove BillingCacheService from module providers
- Delete unused billing-cache.service.ts file

This simplifies the architecture by removing unnecessary abstraction
and follows standard caching proxy patterns.

Co-Authored-By: morgan@cal.com <morgan@cal.com>
@ThyMinimalDev ThyMinimalDev force-pushed the devin/billing-cache-service-1758267128 branch from 18d49de to c92d169 Compare September 22, 2025 07:47
@ThyMinimalDev ThyMinimalDev merged commit 8b75bf3 into main Sep 24, 2025
40 checks passed
@ThyMinimalDev ThyMinimalDev deleted the devin/billing-cache-service-1758267128 branch September 24, 2025 10:36
saurabhraghuvanshii pushed a commit to saurabhraghuvanshii/cal.com that referenced this pull request Sep 24, 2025
…ata (calcom#23934)

* feat: add BillingCacheService with 1-hour TTL for team subscription data

- Create BillingCacheService following CalendarsCacheService pattern
- Use teamId-based cache keys with 1-hour TTL (3,600,000 ms)
- Integrate caching into getBillingData method in BillingService
- Add cache invalidation to all webhook handlers:
  - handleStripeSubscriptionDeleted
  - handleStripePaymentSuccess
  - handleStripePaymentFailed
  - handleStripePaymentPastDue
  - handleStripeCheckoutEvents
- Add cache invalidation to cancelTeamSubscription method
- Add RedisModule import to billing module
- Add BillingCacheService to billing module providers
- Add findTeamByPlatformBillingId method to OrganizationsRepository for cache invalidation

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* refactor: implement BillingServiceCachingProxy pattern

- Extract IBillingService interface with all public methods
- Create BillingServiceCachingProxy that implements caching logic
- Remove all caching logic from original BillingService
- Simplify cache invalidation using billing.id = team.id
- Update module to use proxy with proper dependency injection
- Update controller to inject proxy interface
- Remove unused BillingService import from controller

This follows the proxy pattern requested in PR feedback, separating
caching concerns from core billing logic for better maintainability.

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* chore: add e2e for billing check

* chore: eslint rule for blocking importing features from appstore, lib, prisma (calcom#23832)

* eslint rule

* improve

* fix

* improve msg

* chore: fix any types set by devin

* fix: add mising expect in test

* refactor: move cache methods into BillingServiceCachingProxy

- Remove BillingCacheService abstraction as suggested by @keithwillcode
- Move cache methods directly into proxy as private methods
- Update proxy to inject RedisService directly
- Move BillingData type to interface for better type safety
- Remove BillingCacheService from module providers
- Delete unused billing-cache.service.ts file

This simplifies the architecture by removing unnecessary abstraction
and follows standard caching proxy patterns.

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: test and legacy starter

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: morgan@cal.com <morgan@cal.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

billing area: billing, stripe, payments, paypal, get paid ✨ feature New feature or request ready-for-e2e size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants