Skip to content

Conversation

pax2678
Copy link

@pax2678 pax2678 commented Aug 19, 2025

fix inconsistent priceId value in metadata stored in Polar and Convex. priceId is not updated after a user changes the subscription creating confusion.

Changes Summary:

Checkout Creation (lines 52-54):

REMOVED: priceId: productPriceId, from the metadata object
RESULT: Metadata now only contains userId when creating Polar checkouts
Impact:

Polar subscriptions: Will only store userId in metadata (no priceId)
Convex subscriptions: Will only store userId in metadata (no priceId)
Price tracking: Still works via the polarPriceId field in the subscription record
User linking: Preserved via userId in metadata for webhook processing


Summary by cubic

Removed priceId from Polar/Convex metadata and now rely on polarPriceId stored on the subscription. This fixes stale price references and ensures we always read the latest subscription for a user.

  • Bug Fixes
    • Removed priceId from checkout metadata; metadata now only includes userId.
    • Always select the latest subscription by ordering userId queries in descending order.
    • Update polarPriceId on webhook subscription updates; price tracking now uses polarPriceId.

Summary by CodeRabbit

  • Bug Fixes

    • Ensures your latest subscription is always recognized, preventing outdated status from showing.
    • Improves handling of subscription updates to reflect current pricing details accurately.
  • Chores

    • Streamlined checkout metadata and standardized subscription record updates for greater reliability.

pax2678 added 2 commits July 10, 2025 14:30
…dated in Polar. Select the latest subscription when querying subscription based on userId.
…. priceId is not updated after a user changes the subscription creating confusion.

Changes Summary:

Checkout Creation (lines
  52-54):
  - REMOVED: priceId:
  productPriceId, from the metadata
   object
  - RESULT: Metadata now only
  contains userId when creating
  Polar checkouts

Impact:

  - Polar subscriptions: Will only
  store userId in metadata (no
  priceId)
  - Convex subscriptions: Will only
   store userId in metadata (no
  priceId)
  - Price tracking: Still works via
   the polarPriceId field in the
  subscription record
  - User linking: Preserved via
  userId in metadata for webhook
  processing
Copy link

vercel bot commented Aug 19, 2025

@pax2678 is attempting to deploy a commit to the Goshen Labs Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

coderabbitai bot commented Aug 19, 2025

Walkthrough

The checkout flow no longer stores priceId in metadata. Subscription queries now sort by createdAt descending and use the latest record. The subscription.updated webhook now persists polarPriceId (from price_id) onto the subscription record.

Changes

Cohort / File(s) Summary
Subscriptions logic
convex/subscriptions.ts
Removed priceId from checkout metadata; updated subscription lookups to order by createdAt desc and select the latest; webhook handler for subscription.updated now saves polarPriceId from webhook price_id into the subscription record.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Backend as Subscriptions API
  Client->>Backend: createCheckout()
  Note right of Backend: Do not include priceId in metadata
  Backend-->>Client: Checkout session created
Loading
sequenceDiagram
  participant Webhook as Polar Webhook
  participant Backend as Subscriptions API
  participant DB as Subscriptions DB
  Webhook->>Backend: subscription.updated(price_id)
  Backend->>DB: Update subscription.polarPriceId = price_id
  DB-->>Backend: OK
Loading
sequenceDiagram
  participant Client
  participant Backend as Subscriptions API
  participant DB as Subscriptions DB
  Client->>Backend: checkUserSubscriptionStatus(...)
  Backend->>DB: Query subscriptions ORDER BY createdAt DESC
  DB-->>Backend: Latest subscription
  Backend-->>Client: Latest status
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nudge my whiskers at the checkout stream,
No priceId crumbs—just clean, precise gleam.
I hop through dates, pick latest with care,
Webhook winds whisper price into lair.
Subscriptions aligned, carrots in a row—
Thump-thump approves this tidy flow.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Ordering Assumption

The added .order("desc") relies on default sort field (likely createdAt). Confirm the index supports this ordering and that the desired "latest subscription" is reliably returned; otherwise explicitly sort by a timestamp field or adjust the index.

const subscription = await ctx.db
  .query("subscriptions")
  .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
  .order("desc")  // Order by createdAt in descending order
  .first();
Data Consistency

When updating subscriptions in the webhook, only polarPriceId and some fields are patched; ensure other fields dependent on price changes remain consistent (e.g., product/plan references) and consider backfilling existing records if priceId was previously stored only in metadata.

await ctx.db.patch(existingSub._id, {
  polarPriceId: args.body.data.price_id,
  amount: args.body.data.amount,
  status: args.body.data.status,

Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Ensure deterministic latest selection

Verify that .order("desc") is supported and orders by the intended field (e.g.,
createdAt). If the index requires an explicit sort key, include it or add a
.filter/projection to guarantee the latest record is returned deterministically.

convex/subscriptions.ts [197-201]

 const subscription = await ctx.db
   .query("subscriptions")
   .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
-  .order("desc")  // Order by createdAt in descending order
+  .order("desc") // Ensure index sorts by createdAt; otherwise, add secondary key
   .first();
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a valid point as Convex's .order("desc") on an index sorts by the indexed fields, which might not be the intended createdAt field, potentially leading to incorrect subscription data retrieval.

Medium
Avoid overwriting price with null

Guard against missing or unexpected price_id shape in the webhook payload before
persisting. Fallback to previous polarPriceId or skip updating it to avoid
clobbering with undefined/null and causing downstream lookups to break.

convex/subscriptions.ts [332-337]

 await ctx.db.patch(existingSub._id, {
-  polarPriceId: args.body.data.price_id,
+  polarPriceId: args.body.data.price_id ?? existingSub.polarPriceId,
   amount: args.body.data.amount,
   status: args.body.data.status,
-  currentPeriodStart: new Date(
-    args.body.data.current_period_start
-  ),
-  ...
+  currentPeriodStart: new Date(args.body.data.current_period_start),
+  // ...
 });

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue where polarPriceId could be overwritten with a nullish value from the webhook, and proposes a robust solution using the nullish coalescing operator.

Medium
General
Align identifiers across queries

Normalize tokenIdentifier usage: subsequent queries use user.tokenIdentifier as
the subscription key; ensure this matches the subscriptions' userId field. If
userId actually stores a different identifier (e.g., _id), use that consistently
to prevent mismatched lookups and false negatives.

convex/subscriptions.ts [188-191]

 const user = await ctx.db
   .query("users")
   .withIndex("by_token", (q) => q.eq("tokenIdentifier", tokenIdentifier))
   .unique();
+// Ensure the same identifier is used in both places:
+const userKey = user?.tokenIdentifier; // or user._id if subscriptions.userId stores document id
+const subscription = await ctx.db
+  .query("subscriptions")
+  .withIndex("userId", (q) => q.eq("userId", userKey))
+  .order("desc")
+  .first();
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion raises a valid point about ensuring identifier consistency between the users and subscriptions tables, which is crucial for correct data lookups, although it's a verification rather than a definite bug.

Low
  • More

Copy link

@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: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dbafa74 and 22ef41e.

📒 Files selected for processing (1)
  • convex/subscriptions.ts (5 hunks)
⏰ 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). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (3)
convex/subscriptions.ts (3)

187-187: Whitespace-only change — safe to ignore

No functional impact.


196-196: Whitespace-only change — safe to ignore

No functional impact.


333-345: Persisting polarPriceId on subscription updates is correct

Storing args.body.data.price_id into polarPriceId keeps your subscription record in sync with Polar even when the price changes after creation. This complements the insert path which already sets polarPriceId.

Comment on lines +200 to 201
.order("desc") // Order by createdAt in descending order
.first();
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

“Order by createdAt” isn’t achieved; current index doesn’t guarantee latest subscription

.withIndex("userId", …).order("desc") will order by the index key (userId) and then by internal doc id, not by createdAt. This can return an arbitrary subscription for the user and lead to incorrect hasActiveSubscription results.

Prefer ordering by a time field that exists on the subscription document and is indexed as a secondary key. Since you already persist startedAt, index on (userId, startedAt) and order desc to get the most recent subscription for the user.

Apply this change in the query to use a compound index:

-      .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
-      .order("desc")  // Order by createdAt in descending order
+      .withIndex("by_userId_startedAt", (q) => q.eq("userId", user.tokenIdentifier))
+      .order("desc")  // Latest by startedAt

Add the corresponding index in your Convex schema (example):

// convex/schema.ts
defineTable({
  // ... other fields ...
  userId: v.string(),
  startedAt: v.number(), // already present in your inserts
})
  .index("by_userId", ["userId"])
  .index("by_userId_startedAt", ["userId", "startedAt"]);

Verification script to confirm the index exists in your schema and to find all places to update:


🏁 Script executed:

#!/bin/bash
# Look for Convex schema and check for the compound index and subscriptions table fields.
fd -a -t f 'schema' | while read -r f; do
  echo "== $f ==";
  rg -n -C2 -H 'defineTable|subscriptions|index\(|startedAt' "$f";
done

# Find all queries over "subscriptions" using the "userId" index
rg -n -C2 --type=ts '\.query\("subscriptions"\).*?\.withIndex\("userId"' convex

Length of output: 2415


#!/bin/bash
# Confirm the subscription query uses withIndex("userId") and ordering on createdAt
rg -n 'withIndex\("userId"' -C3 --type=ts convex/subscriptions.ts
rg -n 'query\("subscriptions"' -C3 --type=ts convex/subscriptions.ts

#!/bin/bash
# Display the query on subscriptions and its index usage around withIndex("userId")
rg -nP 'query\("subscriptions".*?withIndex\("userId"' -C4 convex/subscriptions.ts

Update subscription query to use a compound index on startedAt and add that index

The current query in convex/subscriptions.ts is still using:

.withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
.order("desc")  // Order by createdAt in descending order
.first();

That will sort by the index key (userId) and then by internal document ID—not by your timestamp field—so you won’t reliably get the most recent subscription.

Please make the following critical fixes:

• In convex/subscriptions.ts (around lines 200–201), change to:

-      .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
-      .order("desc")  // Order by createdAt in descending order
+      .withIndex("by_userId_startedAt", (q) =>
+        q.eq("userId", user.tokenIdentifier)
+      )
+      .order("desc")  // Latest by startedAt
       .first();

• In your schema file (convex/schema.ts), add the compound index on startedAt:

 subscriptions: defineTable({
   userId: v.optional(v.string()),
   // … other fields …
   startedAt: v.optional(v.number()),
 })
-  .index("userId", ["userId"])
+  .index("userId", ["userId"])
+  .index("by_userId_startedAt", ["userId", "startedAt"])
   .index("polarId", ["polarId"]),

After deploying these changes, your .order("desc") will correctly return the subscription with the highest startedAt for a given user.

🤖 Prompt for AI Agents
In convex/subscriptions.ts around lines 200–201, the query uses
.withIndex("userId", ...) which sorts by userId and internal ID instead of your
timestamp; update the query to use a compound index that includes startedAt
(e.g., .withIndex("userId_startedAt", q => q.eq("userId",
user.tokenIdentifier)).order("desc").first()) so the .order("desc") sorts by
startedAt; then in convex/schema.ts add a compound index declaration for the
subscriptions table that includes userId and startedAt (e.g., an index named
userId_startedAt with keys [userId, startedAt]) and deploy the schema so the
query can use that new index.

Comment on lines +237 to 238
.order("desc") // Order by createdAt in descending order
.first();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Same ordering issue here — switch to a compound (userId, startedAt) index

This query also won’t reliably return the latest subscription for the user. Use the same index and ordering approach as above.

Apply this change:

-      .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
-      .order("desc")  // Order by createdAt in descending order
+      .withIndex("by_userId_startedAt", (q) => q.eq("userId", user.tokenIdentifier))
+      .order("desc")  // Latest by startedAt
🤖 Prompt for AI Agents
In convex/subscriptions.ts around lines 237-238, the query currently orders by
createdAt desc which won’t reliably return the user’s latest subscription;
switch to using the compound index on (userId, startedAt) and order by startedAt
descending when querying for a user's latest subscription. Update the query to
filter by userId, use the compound index (userId, startedAt), order by startedAt
in descending order and then take the first result, and ensure the compound
index exists in the schema if not already defined.

Comment on lines +265 to 266
.order("desc") // Order by createdAt in descending order
.first();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Same ordering issue — ensure you truly fetch the latest subscription

Use the (userId, startedAt) compound index and order desc to select the most recent subscription.

Apply this change:

-      .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
-      .order("desc")  // Order by createdAt in descending order
+      .withIndex("by_userId_startedAt", (q) => q.eq("userId", user.tokenIdentifier))
+      .order("desc")  // Latest by startedAt
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.order("desc") // Order by createdAt in descending order
.first();
.withIndex("by_userId_startedAt", (q) => q.eq("userId", user.tokenIdentifier))
.order("desc") // Latest by startedAt
.first();
🤖 Prompt for AI Agents
In convex/subscriptions.ts around lines 265-266, the query currently orders by
createdAt and may not reliably return the latest subscription; change the query
to use the (userId, startedAt) compound index, order by startedAt descending and
keep the single-row fetch (first/limit 1) so it reliably returns the most recent
subscription for the user.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant