Conversation
Co-authored-by: otdoges <otdoges@proton.me>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎
Codebase SummaryZapDev is an AI-powered development platform built with Next.js and a modern tech stack (React, TypeScript, Tailwind CSS, and more). It enables real-time code generation and sandboxed execution with a conversational interface. The repository includes features like authentication with Clerk, subscription management, and project persistence among others. PR ChangesThis PR integrates polar.sh with Stack Auth to enable checkout functionality and manage subscriptions more effectively. It introduces a new Polar checkout creation endpoint, a webhook handler for syncing subscription events with Convex, and a Polar client utility in the codebase. Setup Instructions
Generated Test Cases1: Successful Polar Checkout Creation ❗️❗️❗️Description: Tests the complete user journey of initiating a Polar checkout. This includes verifying that a logged-in user can trigger the checkout process with a valid product ID, and the system successfully creates a checkout session and redirects the user to the provided checkout URL. Prerequisites:
Steps:
Expected Result: The user sees a successful redirection to the checkout URL provided in the API response, confirming that the Polar checkout session was created successfully. 2: Handle Missing Product ID Error in Polar Checkout ❗️❗️Description: Tests the error handling when a checkout request is made without a required productId. This scenario verifies that the system returns an appropriate error message and status code, and that the UI informs the user of the missing information. Prerequisites:
Steps:
Expected Result: The user remains on the same page and sees an error message stating 'Missing productId', ensuring that the system does not proceed with an incomplete checkout request. 3: Display Error When Polar Checkout Session Creation Fails ❗️❗️❗️Description: Tests the user experience when the backend fails to create a Polar checkout session due to unexpected issues (e.g., network error, invalid POLAR_ACCESS_TOKEN). This test ensures that errors are caught, logged, and communicated to the user via the UI. Prerequisites:
Steps:
Expected Result: The UI correctly displays an error message indicating that there was an issue creating the checkout session, and no redirection occurs. Raw Changes AnalyzedFile: src/app/api/polar/create-checkout/route.ts
Changes:
@@ -1,8 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { getUser } from "@/lib/stack-auth";
+import { getPolarClient } from "@/lib/polar";
-// NOTE: Polar checkout will be implemented after Stack Auth is fully configured
-// This is a placeholder route for now
export async function POST(req: NextRequest) {
try {
// Authenticate user with Stack Auth
@@ -15,11 +14,31 @@ export async function POST(req: NextRequest) {
);
}
- // TODO: Implement Polar checkout once Stack Auth is configured with proper API keys
- return NextResponse.json(
- { error: "Polar checkout not yet configured. Please set up Stack Auth first." },
- { status: 501 }
- );
+ const body = await req.json();
+ const { productId } = body;
+
+ if (!productId) {
+ return NextResponse.json(
+ { error: "Missing productId" },
+ { status: 400 }
+ );
+ }
+
+ const polar = getPolarClient();
+ const appUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
+
+ // Create checkout
+ // We pass userId in metadata so we can link subscription to user in webhook
+ const checkout = await polar.checkouts.create({
+ productId,
+ successUrl: `${appUrl}/dashboard?checkout=success`,
+ customerEmail: user.primaryEmail || undefined,
+ metadata: {
+ userId: user.id,
+ },
+ });
+
+ return NextResponse.json({ url: checkout.url });
} catch (error) {
console.error("Error creating Polar checkout session:", error);
File: src/app/api/webhooks/polar/route.ts
Changes:
@@ -0,0 +1,106 @@
+import { NextRequest, NextResponse } from "next/server";
+import { validateEvent } from "@polar-sh/sdk/webhooks";
+import { ConvexHttpClient } from "convex/browser";
+import { api } from "@/convex/_generated/api";
+import { getPolarClient } from "@/lib/polar";
+
+const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
+
+export async function POST(req: NextRequest) {
+ const requestBody = await req.text();
+ const webhookHeaders: Record<string, string> = {};
+
+ req.headers.forEach((value, key) => {
+ webhookHeaders[key] = value;
+ });
+
+ const webhookSecret = process.env.POLAR_WEBHOOK_SECRET;
+
+ if (!webhookSecret) {
+ console.error("POLAR_WEBHOOK_SECRET is missing");
+ return NextResponse.json({ error: "Configuration error" }, { status: 500 });
+ }
+
+ let event;
+ try {
+ event = validateEvent(requestBody, webhookHeaders, webhookSecret);
+ } catch (error) {
+ console.error("Webhook verification failed:", error);
+ return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
+ }
+
+ try {
+ switch (event.type) {
+ case "subscription.created":
+ case "subscription.updated":
+ case "subscription.active": {
+ const subscription = event.data;
+ const userId = subscription.metadata?.userId as string | undefined;
+
+ if (!userId) {
+ console.warn(`Subscription ${subscription.id} missing userId in metadata`);
+ return NextResponse.json({ received: true });
+ }
+
+ // Try to get product name from payload or fetch it
+ let productName = (subscription as any).product?.name;
+
+ if (!productName) {
+ try {
+ const polar = getPolarClient();
+ // Polar SDK types might differ, but usually there's a way to get product
+ const product = await polar.products.get({ id: subscription.product_id });
+ productName = product.name;
+ } catch (e) {
+ console.error("Failed to fetch product details", e);
+ productName = "Subscription";
+ }
+ }
+
+ // Map Polar status to Convex status
+ let status: "incomplete" | "active" | "canceled" | "past_due" | "unpaid" = "active";
+ const s = subscription.status;
+
+ if (["active", "trialing"].includes(s)) status = "active";
+ else if (["incomplete"].includes(s)) status = "incomplete";
+ else if (["past_due"].includes(s)) status = "past_due";
+ else if (["canceled", "incomplete_expired"].includes(s)) status = "canceled";
+ else if (["unpaid"].includes(s)) status = "unpaid";
+
+ await convex.mutation(api.subscriptions.createOrUpdateSubscription, {
+ userId: userId,
+ polarCustomerId: subscription.customer_id,
+ polarSubscriptionId: subscription.id,
+ productId: subscription.product_id,
+ productName: productName,
+ status: status,
+ currentPeriodStart: new Date(subscription.current_period_start).getTime(),
+ currentPeriodEnd: new Date(subscription.current_period_end).getTime(),
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
+ metadata: subscription.metadata,
+ });
+ break;
+ }
+
+ case "subscription.revoked":
+ await convex.mutation(api.subscriptions.revokeSubscription, {
+ polarSubscriptionId: event.data.id,
+ });
+ break;
+
+ case "subscription.canceled":
+ await convex.mutation(api.subscriptions.markSubscriptionForCancellation, {
+ polarSubscriptionId: event.data.id,
+ });
+ break;
+
+ default:
+ console.log(`Unhandled event type: ${event.type}`);
+ }
+ } catch (error) {
+ console.error("Error processing webhook:", error);
+ return NextResponse.json({ error: "Error processing webhook" }, { status: 500 });
+ }
+
+ return NextResponse.json({ received: true });
+}
File: src/lib/polar.ts
Changes:
@@ -0,0 +1,18 @@
+import { Polar } from "@polar-sh/sdk";
+
+let polarInstance: Polar | null = null;
+
+export const getPolarClient = () => {
+ if (!polarInstance) {
+ const accessToken = process.env.POLAR_ACCESS_TOKEN;
+ if (!accessToken) {
+ throw new Error("POLAR_ACCESS_TOKEN is missing");
+ }
+
+ polarInstance = new Polar({
+ accessToken,
+ server: process.env.NODE_ENV === "development" ? "sandbox" : "production",
+ });
+ }
+ return polarInstance;
+};
|
|
Caution Review failedThe pull request is closed. WalkthroughThis PR implements Polar payment integration by adding three components: a Polar API client utility for authenticated requests, a checkout creation endpoint that validates requests and generates secure checkout URLs, and a webhook handler that processes Polar subscription events and synchronizes them with the Convex database. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CheckoutAPI as Checkout API
participant Polar
participant Webhook as Webhook Handler
participant Convex as Convex DB
rect rgba(100, 150, 255, 0.2)
note right of User: Checkout Flow
User->>CheckoutAPI: POST /api/polar/create-checkout<br/>{productId}
activate CheckoutAPI
CheckoutAPI->>CheckoutAPI: Validate productId
CheckoutAPI->>Polar: Create checkout<br/>(productId, userEmail, metadata)
activate Polar
Polar-->>CheckoutAPI: Return checkout URL
deactivate Polar
CheckoutAPI-->>User: {url}
deactivate CheckoutAPI
end
rect rgba(100, 200, 100, 0.2)
note right of Polar: Webhook Flow
Polar->>Webhook: POST /api/webhooks/polar<br/>(signed event)
activate Webhook
Webhook->>Webhook: Verify webhook signature
Webhook->>Webhook: Extract event type & data
alt subscription.created/updated/active
Webhook->>Polar: Fetch product details
activate Polar
Polar-->>Webhook: Product info
deactivate Polar
Webhook->>Convex: createOrUpdateSubscription mutation
else subscription.revoked
Webhook->>Convex: revokeSubscription mutation
else subscription.canceled
Webhook->>Convex: markSubscriptionForCancellation mutation
else default
Webhook->>Webhook: Log unhandled event
end
activate Convex
Convex-->>Webhook: Mutation result
deactivate Convex
Webhook-->>Polar: {status: 200}
deactivate Webhook
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (3)
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. Comment |
|
🚀 Scrapybara Ubuntu instance started! |
Pull Request Review: Polar.sh IntegrationOverall AssessmentThis PR successfully integrates Polar.sh payment and subscription management with Stack Auth. The implementation is well-structured and follows good patterns. However, there are several important security, reliability, and code quality improvements needed before merging. 🔴 Critical Issues1. Missing Webhook Idempotency (Security & Reliability)Location: The webhook handler lacks idempotency protection. If Polar retries the same webhook (network issues, timeouts), you'll process duplicate events leading to:
Fix: Use the existing // After successful processing: 2. Race Condition in Subscription Creation (Data Integrity)Location: Multiple webhook events can arrive simultaneously (created + active). The current check-then-insert pattern has a race condition window. Fix: Use a database constraint or atomic upsert. The Convex mutations already handle this correctly, but consider adding explicit transaction handling if needed. 3. Unvalidated User Input (Security)Location: The
Fix: Validate against a whitelist: if (!VALID_PRODUCT_IDS.includes(productId)) { 4. Missing Environment Variable Validation (Reliability)Location: Runtime errors if Fix:
|
|
🔧 Setting up test environment... Agent Steps |
|
❌ Something went wrong: |
There was a problem hiding this comment.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on December 17
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| productName: productName, | ||
| status: status, | ||
| currentPeriodStart: new Date(subscription.current_period_start).getTime(), | ||
| currentPeriodEnd: new Date(subscription.current_period_end).getTime(), |
There was a problem hiding this comment.
Bug: Invalid date conversion without null checks in webhook
The webhook handler converts subscription.current_period_start and subscription.current_period_end to timestamps without validating they exist or are valid date values. If these fields are undefined or null, new Date() creates an Invalid Date and .getTime() returns NaN. This NaN value is then passed to the Convex mutation, which expects valid numbers, resulting in corrupted subscription records. The webhook returns successfully (200 OK) despite the data corruption, masking the error from Polar's retry mechanism.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case "subscription.canceled": | ||
| await convex.mutation(api.subscriptions.markSubscriptionForCancellation, { | ||
| polarSubscriptionId: event.data.id, | ||
| }); |
There was a problem hiding this comment.
Cancel webhook leaves subscription active
In the Polar webhook handler the subscription.canceled event routes to markSubscriptionForCancellation, which only sets the cancelAtPeriodEnd flag (see convex/subscriptions.ts) and leaves the status untouched. When Polar fires this event after a subscription is actually canceled, our Convex record will stay at its previous status (often active), so entitlement checks will never see the cancellation and users retain access. The canceled event should update the subscription status (e.g., via createOrUpdateSubscription or revokeSubscription) rather than just scheduling cancellation.
Useful? React with 👍 / 👎.
Description
Integrates polar.sh with Stack Auth to enable checkout functionality and manage subscriptions efficiently.
Changes
Implemented Polar checkout creation, added a webhook handler for Polar events to sync subscriptions with Convex, and created a Polar client utility.
Note
Adds a Polar checkout API and a webhook handler that validates events and syncs subscription state to Convex via a shared Polar client.
src/app/api/polar/create-checkout/route.ts):productId, creates Polar checkout (successUrl,customerEmail,metadata.userId), returns checkouturl.src/app/api/webhooks/polar/route.ts):POLAR_WEBHOOK_SECRET, handlessubscription.created|updated|active|revoked|canceled.api.subscriptions.*; fetches product name when needed.src/lib/polar.ts):getPolarClientusingPOLAR_ACCESS_TOKEN; selectssandbox/productionbyNODE_ENV.Written by Cursor Bugbot for commit 9c0f620. Configure here.
Summary by CodeRabbit
Release Notes
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.