Skip to content

fix one-time-stackable#925

Merged
BilalG1 merged 22 commits intodevfrom
fix-one-time-stackable
Oct 7, 2025
Merged

fix one-time-stackable#925
BilalG1 merged 22 commits intodevfrom
fix-one-time-stackable

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Oct 4, 2025

High-level PR Summary

This PR fixes a bug in the one-time purchase validation logic by allowing stackable offers to be purchased multiple times. Previously, the system blocked duplicate one-time purchases regardless of whether the offer was marked as stackable. The fix adds a check to only prevent duplicate purchases when the offer's stackable property is not true, enabling customers to purchase stackable offers more than once.

⏱️ Estimated Review Time: 15-30 minutes

💡 Review Order Suggestion
Order File Path
1 apps/backend/src/lib/payments.tsx
2 apps/backend/src/lib/payments.test.tsx

Need help? Join our Discord


Important

Fixes purchase validation to allow multiple purchases of stackable products and updates tests accordingly.

  • Behavior:
    • Fixes bug in validatePurchaseSession in payments.tsx to allow multiple purchases of stackable products.
    • Updates error message for non-stackable products to "Customer already has purchased this product; this product is not stackable".
  • Tests:
    • Adds test cases in payments.test.tsx to verify stackable and non-stackable purchase logic.
    • Ensures stackable products can be purchased multiple times, while non-stackable products cannot.
  • Misc:
    • Minor updates to error messages and test descriptions for clarity.

This description was created by Ellipsis for 113d035. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features
    • Improved default product handling per catalog with clear errors when multiple defaults are configured.
  • Bug Fixes
    • Unified validation for non-stackable products across subscriptions and one-time purchases, preventing duplicate ownership.
    • Standardized error message when attempting to repurchase a non-stackable product: “Customer already has purchased this product; this product is not stackable”.
  • Tests
    • Updated unit and end-to-end tests to reflect non-stackable product behavior and the new error messaging.
    • Adjusted scenarios and expectations around default product selection and duplicate purchase blocking.

@vercel
Copy link
Copy Markdown

vercel Bot commented Oct 4, 2025

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

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Oct 7, 2025 4:32pm
stack-dashboard Ready Ready Preview Comment Oct 7, 2025 4:32pm
stack-demo Ready Ready Preview Comment Oct 7, 2025 4:32pm
stack-docs Ready Ready Preview Comment Oct 7, 2025 4:32pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 4, 2025

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The pull request title “fix one-time-stackable” is vague and does not clearly indicate the main change, making it hard for teammates to understand that this fixes the stackable one-time purchase behavior and allows multiple purchases of stackable offers. Consider renaming the title to something more descriptive of the core change, for example “Allow multiple purchases of stackable one-time products”, so that it clearly conveys the intent of the PR.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The pull request description begins with the required contributing guidelines comment and includes a clear high-level summary, detailed explanations of behavior changes, test updates, and context, aligning with the repository template and providing complete guidance for reviewers.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-one-time-stackable

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 and usage tips.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Summary

This PR fixes a bug in the one-time purchase validation logic for stackable offers in the payments system. The main change corrects the `validatePurchaseSession` function to properly respect the `stackable` property of offers when determining whether to allow duplicate purchases.

Previously, the code was incorrectly blocking all duplicate one-time purchases regardless of whether the offer was configured as stackable. The fix adds a condition && offer.stackable !== true to the validation check, ensuring that only non-stackable offers prevent duplicate purchases. This aligns the one-time purchase logic with the existing subscription validation pattern already present in the codebase.

The change is accompanied by comprehensive test coverage that validates three key scenarios: allowing duplicate purchases for stackable offers, blocking one-time purchases when subscriptions exist for non-stackable offers, and allowing one-time purchases when subscriptions exist for stackable offers. This ensures the stackable offer feature works correctly across different purchase combinations.

Important Files Changed

Changed Files
Filename Score Overview
apps/backend/src/lib/payments.tsx 5/5 Fixed validation logic to respect stackable property for one-time purchases
apps/backend/src/lib/payments.test.tsx 5/5 Added comprehensive test coverage for stackable offer validation scenarios

Confidence score: 5/5

  • This PR is safe to merge with minimal risk
  • Score reflects a simple, well-tested bug fix that aligns existing inconsistent logic
  • No files require special attention

Sequence Diagram

sequenceDiagram
    participant User
    participant API
    participant PaymentService
    participant Database

    User->>API: "Create Purchase Session"
    API->>PaymentService: "validatePurchaseSession()"
    
    PaymentService->>Database: "ensureCustomerExists()"
    Database-->>PaymentService: "Customer validation result"
    
    PaymentService->>PaymentService: "Validate offer prices and stackability"
    
    PaymentService->>Database: "findMany() - get existing one-time purchases"
    Database-->>PaymentService: "Existing one-time purchases"
    
    PaymentService->>PaymentService: "Check if offer already purchased (if not stackable)"
    
    PaymentService->>PaymentService: "getSubscriptions()"
    PaymentService->>Database: "findMany() - get customer subscriptions"
    Database-->>PaymentService: "Customer subscriptions"
    
    PaymentService->>PaymentService: "Check subscription conflicts (if not stackable)"
    
    PaymentService->>PaymentService: "Check group conflicts for one-time purchases"
    
    PaymentService->>PaymentService: "Identify conflicting group subscriptions"
    
    PaymentService-->>API: "Validation result with conflicts"
    API-->>User: "Purchase session validation response"
Loading

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Copy Markdown

@recurseml recurseml Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by RecurseML

🔍 Review performed on a914562..7e828ea

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (1)

apps/backend/src/lib/payments.tsx

⏭️ Files skipped (1)
  Locations  
apps/backend/src/lib/payments.test.tsx

Comment thread apps/backend/src/lib/payments.tsx Outdated
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Oct 6, 2025
Base automatically changed from fix-ungrouped-include-by-default to dev October 7, 2025 16:19
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/backend/src/lib/payments.tsx (1)

304-324: Consider explicit destructuring for clarity.

The logic correctly handles multiple default products per catalog and throws a descriptive error when misconfigured. However, line 312 uses product as a tuple, which could be more explicit.

Consider this refactor for improved readability:

     if (defaultCatalogProducts.length > 0) {
-      const product = defaultCatalogProducts[0];
+      const [productId, productDef] = defaultCatalogProducts[0];
       subscriptions.push({
         id: null,
-        productId: product[0],
-        product: product[1],
+        productId,
+        product: productDef,
         quantity: 1,
         currentPeriodStart: DEFAULT_PRODUCT_START_DATE,
         currentPeriodEnd: null,
         status: SubscriptionStatus.active,
         createdAt: DEFAULT_PRODUCT_START_DATE,
         stripeSubscriptionId: null,
       });
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f146ff2 and 314841f.

📒 Files selected for processing (4)
  • apps/backend/src/lib/payments.test.tsx (5 hunks)
  • apps/backend/src/lib/payments.tsx (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • apps/backend/src/lib/payments.test.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • apps/backend/src/lib/payments.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • apps/backend/src/lib/payments.test.tsx
🧬 Code graph analysis (2)
apps/backend/src/lib/payments.tsx (1)
packages/stack-shared/src/utils/errors.tsx (2)
  • StackAssertionError (69-85)
  • StatusError (152-261)
apps/backend/src/lib/payments.test.tsx (1)
apps/backend/src/lib/payments.tsx (2)
  • validatePurchaseSession (389-482)
  • getSubscriptions (264-345)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: docker
  • GitHub Check: Security Check
🔇 Additional comments (8)
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (1)

793-846: LGTM! Test correctly validates non-stackable one-time purchase blocking.

The test properly validates that:

  • The product is configured as non-stackable (stackable: false on line 803)
  • Duplicate purchases are blocked with status 400 (line 844)
  • The error message matches the new unified format (line 845)

These changes align with the PR objective to enforce stackability constraints for one-time purchases.

apps/backend/src/lib/payments.tsx (1)

443-449: LGTM! Consolidated stackability validation is correct.

The validation logic correctly:

  • Checks both active subscriptions and existing one-time purchases for non-stackable products
  • Only validates when a specific productId is provided (important for inline products)
  • Uses the unified error message format consistent across the codebase

The implementation properly addresses the PR objective to allow stackable products to be purchased multiple times while blocking non-stackable products.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (1)

793-846: LGTM! Legacy API test maintains consistency.

The test correctly validates the same non-stackable behavior for the legacy /api/v1 endpoint, ensuring backward compatibility. The changes mirror those in the primary test file and use the unified error message format.

apps/backend/src/lib/payments.test.tsx (5)

845-845: LGTM! Error message updated to match unified format.

The test expectation correctly reflects the new consolidated error message introduced in the core validation logic.


916-952: LGTM! Test validates stackable product behavior.

This test correctly validates that:

  • Duplicate purchases are allowed when product.stackable is true (line 940)
  • Quantity > 1 is permitted for stackable products (line 947)
  • The validation succeeds without throwing an error

This test directly addresses the PR objective to allow stackable products to be purchased multiple times.


954-1007: LGTM! Test validates subscription blocking for non-stackable products.

This test correctly validates that:

  • Non-stackable products (line 965) cannot be repurchased when an active subscription exists
  • The validation checks subscriptions in addition to one-time purchases
  • The unified error message is used (line 1006)

This ensures the consolidated stackability check works correctly across both purchase types.


1009-1065: LGTM! Test validates allowing stackable products with existing subscriptions.

This test correctly validates that:

  • Stackable products (line 1020) can be purchased multiple times
  • Existing subscriptions don't block additional purchases for stackable products
  • The validation succeeds without throwing an error

This is the complementary test case to the non-stackable scenario, ensuring complete coverage of the stackability logic.


1121-1121: LGTM! Terminology updates improve clarity.

The test descriptions and error messages have been updated to use:

  • "products" instead of "offers" (line 1121)
  • "catalogs" instead of "groups" (lines 1166, 1205)

These changes align with the terminology used in the core implementation and improve consistency across the codebase.

Also applies to: 1166-1166, 1205-1205

@github-actions github-actions Bot assigned BilalG1 and unassigned N2D4 Oct 7, 2025
@BilalG1 BilalG1 merged commit e7f2cc8 into dev Oct 7, 2025
23 checks passed
@BilalG1 BilalG1 deleted the fix-one-time-stackable branch October 7, 2025 17:37
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