🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

fix(billing): deny entitlement for a feature with no products - #1858

Merged
rohilsurana merged 1 commit into
mainfrom
fix/billing-entitlement-empty-product-ids
Aug 6, 2026
Merged

fix(billing): deny entitlement for a feature with no products#1858
rohilsurana merged 1 commit into
mainfrom
fix/billing-entitlement-empty-product-ids

Conversation

@rohilsurana

Copy link
Copy Markdown
Member

What

CheckFeatureEntitlement wrongly returns true for a feature that is attached to no product. entitlement.Service.Check does productService.List(Filter{ProductIDs: feature.ProductIDs}), and when feature.ProductIDs is empty the store treats the empty id filter as no filter (billing_planbilling_product_repository.go: if len(flt.ProductIDs) > 0), so it returns every product. The plan-bound product is in that list, its plan_ids matches an active subscription, and Check grants the feature to any active subscriber.

Why it matters

A feature ends up with empty ProductIDs whenever it is removed from its last product — the exact effect of RemoveFeatureFromProduct (called by updateProductFeatures when a product update drops a feature). So retiring a feature would silently leave every subscriber entitled to it. This is an over-grant (grants access it should deny), independent of the reconcile/Stripe path.

Fix

Guard in Check: a feature attached to no product cannot belong to any plan, so return false before the list call.

if len(feature.ProductIDs) == 0 {
    return false, nil
}

The fix lives in Check, not in the store's empty-filter behaviour, because other callers rely on "empty filter = all products".

Test

Added TestService_Check_FeatureWithNoProducts: an active subscriber, a feature with empty ProductIDs. It asserts Check returns false and leaves the ProductService.List mock unset, so the test fails if Check ever calls List again (a regression guard). Verified it fails without the guard (the mock rejects the unexpected List call) and passes with it.

How it was found

Surfaced while testing the BillingProduct reconcile feature-remove flow in a local sandbox: after detaching a feature, GetPlan correctly dropped it, but the entitlement check kept returning true.

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview Aug 6, 2026 11:51am

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5302c8d4-62fa-40a2-95d4-535264e38b53

📥 Commits

Reviewing files that changed from the base of the PR and between 5c7721f and df3164e.

📒 Files selected for processing (2)
  • billing/entitlement/check_no_products_test.go
  • billing/entitlement/service.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Features with no associated products now correctly return no entitlement.
    • Prevented empty product filters from unintentionally matching all products.
    • Added coverage to verify this behavior without errors.

Walkthrough

Service.Check now denies features with no associated products before product lookup. A test verifies the false, error-free result and confirms that no product query occurs.

Changes

Entitlement check

Layer / File(s) Summary
Guard empty product associations
billing/entitlement/service.go, billing/entitlement/check_no_products_test.go
Service.Check returns (false, nil) when a feature has no product IDs. The test verifies subscription and feature lookups and leaves the product mock unset to detect unexpected product queries.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@rohilsurana
rohilsurana marked this pull request as ready for review August 6, 2026 11:56
@rohilsurana
rohilsurana merged commit c21aa1c into main Aug 6, 2026
8 checks passed
@rohilsurana
rohilsurana deleted the fix/billing-entitlement-empty-product-ids branch August 6, 2026 15:11
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