-
Notifications
You must be signed in to change notification settings - Fork 26
Refactor authentication and subscription handling; Update routes and components to support Pro access checks. #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…components to support Pro access checks.
|
@Vanshika814 is attempting to deploy a commit to the devsuraj Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds Razorpay-first subscription plumbing (webhook, service, hook, API), enforces pro-gated access and plan-based generation limits, replaces Stripe checkout/session logic with Razorpay flows, updates UI routing/CTAs based on entitlement, and includes minor editor and docker-compose tweaks. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Page as /generate-pro
participant Clerk
participant SubSvc as subscriptionService
User->>Page: GET /generate-pro
Page->>Clerk: getUser()
alt not signed in
Page-->>User: redirect /sign-in
else signed in
Page->>SubSvc: canAccessProFeatures(userId)
alt pro
Page-->>User: render FlashcardPro
else not pro
Page-->>User: redirect /pricing?upgrade=pro
end
end
sequenceDiagram
autonumber
actor Client
participant API as POST /api/generate
participant SubSvc as subscriptionService
participant DB as Prisma
participant Gen as flashcardService
Client->>API: POST text (text/plain)
API->>SubSvc: getUserPlan(userId)
API->>DB: count flashcards this month
alt limit exceeded
API-->>Client: 403 monthly limit reached
else allowed
API->>Gen: generateFlashcards(text)
Gen-->>API: generated[]
API-->>Client: 200 { flashcards }
end
sequenceDiagram
autonumber
participant Razorpay
participant Webhook as POST /api/razorpay/webhook
participant DB as Prisma
participant SubSvc as subscriptionService
Razorpay->>Webhook: event + signature
Webhook->>Webhook: verify signature (HMAC-SHA256)
alt invalid
Webhook-->>Razorpay: 400 invalid signature
else valid
alt payment.captured
Webhook->>DB: update payment -> COMPLETED
Webhook->>SubSvc: updateSubscriptionStatus(userId, plan, ACTIVE, expiresAt)
Webhook-->>Razorpay: 200
else payment.failed
Webhook->>DB: update payment -> FAILED
Webhook-->>Razorpay: 200
else order.paid
Webhook->>DB: mark related payments COMPLETED
Webhook-->>Razorpay: 200
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Potential focus areas:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)src/app/api/razorpay/webhook/route.ts (1)
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/api/generate/route.ts (1)
56-64: Enforce desiredCount when generating flashcards
generateFlashcardscurrently ignoresdesiredCountand always returns 10 cards. Inroute.ts, either:
- Extend the service signature to
generateFlashcards(text: string, count: number), update its prompt toCreate ${count} flashcards…, and passdesiredCount;- Or post-process results:
const all = await flashcardService.generateFlashcards(text); const flashcards = all.slice(0, desiredCount);
🧹 Nitpick comments (5)
src/app/result/page.tsx (2)
104-104: Minor formatting: Extra leading whitespace.Remove the extra space before the
<p>tag for consistency.Apply this diff:
- <p className="text-muted-foreground mb-4">Your payment failed. Please try again.</p> + <p className="text-muted-foreground mb-4">Your payment failed. Please try again.</p>
15-31: ResultContent should verify payment server-side instead of trusting the URL param. While you verify payment insrc/app/pricing/page.tsxvia/api/razorpay/verify-payment, the result page still readspaymentdirectly from the query. This can confuse users who craft URLs. Insrc/app/result/page.tsx, fetch and confirm the user’s subscription or payment status from a server endpoint (or use a signed, time-limited token) before showing success.src/app/api/razorpay/webhook/route.ts (1)
62-127: Validate payment amount against expected plan pricing.The webhook processes payments without verifying that the captured amount matches the expected price for the subscription plan. This could allow users to gain Pro access by paying incorrect amounts if the order/payment records are manipulated.
Add a validation step before updating the subscription:
if (order) { + // Validate payment amount matches expected plan price + const expectedAmounts = { + free: 0, + basic: 500, // Example prices in paise/cents + pro: 2000, + orgs: 10000 + }; + + const expectedAmount = expectedAmounts[order.plan as keyof typeof expectedAmounts]; + if (payment.amount !== expectedAmount) { + console.error('❌ Payment amount mismatch:', { + expected: expectedAmount, + received: payment.amount, + plan: order.plan + }); + return; + } + // Calculate subscription datessrc/components/core/flash-card-pro.tsx (1)
18-91: Reduce code duplication with the standard flashcard component.
FlashcardProduplicates 90+ lines fromflash-card.tsxwith only minor differences (local storage key, icon, badge, button styling). This violates the DRY principle and increases maintenance burden.Refactor into a single component with a prop to toggle Pro features:
// flash-card.tsx interface FlashcardProps { isPro?: boolean; } export default function Flashcard({ isPro = false }: FlashcardProps) { const { user } = useUser(); const storageKey = isPro ? 'draft-flashcards-pro' : 'draft-flashcards'; const [flashcards, setFlashcards] = useLocalStorage<FlashcardInput[]>(storageKey, []); // ... rest of logic return ( <div className="flex flex-col items-center justify-center min-h-screen p-4 md:p-10"> <Card className="w-full lg:max-w-[70vw]"> <CardHeader> {isPro ? ( <div className="flex items-center justify-between"> <CardTitle className="flex items-center space-x-2"> <Crown className="w-5 h-5 text-purple-600" /> <span>Pro Flashcard Generator</span> </CardTitle> <Badge variant="secondary" className="bg-purple-100 text-purple-800"> PRO </Badge> </div> ) : ( <CardTitle>Flashcard Generator</CardTitle> )} </CardHeader> {/* ... rest of component */} </Card> </div> ); }Then use
<Flashcard isPro={true} />in the Pro page.src/lib/services/subscription.service.ts (1)
99-121: Consider adding type safety for plan and status parameters.The
updateSubscriptionStatusmethod acceptsplanandstatusas strings without validation. Invalid values could be persisted to the database.Add type guards or enums:
// types.ts export enum SubscriptionPlan { Free = 'free', Basic = 'basic', Pro = 'pro', Orgs = 'orgs' } export enum SubscriptionStatus { Active = 'active', Expired = 'expired', Cancelled = 'cancelled' } // subscription.service.ts async updateSubscriptionStatus( userId: string, plan: SubscriptionPlan, // typed parameter status: SubscriptionStatus, // typed parameter expiresAt: Date ): Promise<void> { // ... implementation }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
.vscode/settings.json(1 hunks)docker-compose.yml(0 hunks)src/app/(auth)/sign-in/[[...sign-in]]/page.tsx(1 hunks)src/app/(dashboard)/generate-pro/page.tsx(1 hunks)src/app/api/checkout-sessions/route.ts(1 hunks)src/app/api/generate/route.ts(2 hunks)src/app/api/razorpay/webhook/route.ts(1 hunks)src/app/api/user/subscription-status/route.ts(1 hunks)src/app/flashcards/page.tsx(2 hunks)src/app/page.tsx(3 hunks)src/app/pricing/page.tsx(4 hunks)src/app/result/page.tsx(3 hunks)src/components/Footer.tsx(2 hunks)src/components/Navbar.tsx(4 hunks)src/components/core/flash-card-pro.tsx(1 hunks)src/components/core/flash-card.tsx(1 hunks)src/components/core/generate.tsx(2 hunks)src/hooks/useSubscription.ts(1 hunks)src/lib/services/subscription.service.ts(1 hunks)src/middleware.ts(1 hunks)
💤 Files with no reviewable changes (1)
- docker-compose.yml
🧰 Additional context used
🧬 Code graph analysis (14)
src/app/api/checkout-sessions/route.ts (1)
src/app/api/razorpay/webhook/route.ts (1)
POST(8-60)
src/lib/services/subscription.service.ts (1)
src/lib/database.ts (1)
prisma(7-9)
src/components/Navbar.tsx (1)
src/hooks/useSubscription.ts (1)
useSubscription(15-62)
src/hooks/useSubscription.ts (1)
src/lib/services/subscription.service.ts (1)
SubscriptionStatus(3-9)
src/app/page.tsx (1)
src/hooks/useSubscription.ts (1)
useSubscription(15-62)
src/app/flashcards/page.tsx (1)
src/hooks/useSubscription.ts (1)
useSubscription(15-62)
src/components/Footer.tsx (1)
src/hooks/useSubscription.ts (1)
useSubscription(15-62)
src/app/api/razorpay/webhook/route.ts (1)
src/lib/database.ts (1)
prisma(7-9)
src/app/(dashboard)/generate-pro/page.tsx (2)
src/lib/services/subscription.service.ts (1)
subscriptionService(124-124)src/components/core/flash-card-pro.tsx (1)
FlashcardPro(18-91)
src/components/core/flash-card.tsx (1)
src/components/ui/button.tsx (1)
Button(56-56)
src/components/core/flash-card-pro.tsx (7)
src/lib/hooks/useLocalStorage.ts (1)
useLocalStorage(3-31)src/types/index.ts (1)
FlashcardInput(52-55)src/components/common/ErrorBoundary.tsx (1)
ErrorBoundary(19-82)src/components/flashcards/FlashcardGenerator.tsx (1)
FlashcardGenerator(33-107)src/components/common/LoadingSpinner.tsx (1)
LoadingSpinner(10-31)src/components/flashcards/FlashcardViewer.tsx (1)
FlashcardViewer(16-123)src/components/flashcards/FlashcardSaveDialog.tsx (1)
FlashcardSaveDialog(24-186)
src/app/pricing/page.tsx (1)
src/components/ui/use-toast.ts (2)
useToast(194-194)toast(194-194)
src/app/api/generate/route.ts (3)
src/lib/services/subscription.service.ts (1)
subscriptionService(124-124)src/lib/database.ts (1)
prisma(7-9)src/types/index.ts (1)
ApiError(124-130)
src/app/api/user/subscription-status/route.ts (1)
src/lib/services/subscription.service.ts (1)
subscriptionService(124-124)
🪛 Biome (2.1.2)
src/components/core/generate.tsx
[error] 68-68: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
(parse)
🔇 Additional comments (38)
src/app/result/page.tsx (3)
1-13: LGTM!The imports are appropriate and the comment clearly indicates the architectural shift away from Stripe.
33-122: LGTM!The UI structure is well-organized with clear success, error, and failure states. The responsive design with mobile-first approach, loading skeleton, and actionable CTAs provide a good user experience.
124-163: LGTM!Excellent use of React Suspense with a matching skeleton fallback. This follows Next.js best practices for handling search params and provides a smooth loading experience.
src/components/core/flash-card.tsx (1)
72-72: LGTM! Consistent spacing improvement.The added top margin improves vertical spacing for the "View Saved Flashcards" button and aligns with the same spacing applied in the Pro variant.
src/lib/services/subscription.service.ts (1)
15-70: LGTM! Robust subscription status handling.The method correctly:
- Handles missing users with safe defaults
- Computes expiration status
- Restricts Pro access to paid plans (basic, pro, orgs)
- Logs detailed status for debugging
- Returns safe defaults on errors
src/app/flashcards/page.tsx (1)
11-11: LGTM! Pro-aware navigation logic.The integration of
useSubscriptioncorrectly routes users to/generate-prowhen they have Pro access, and/generateotherwise. This aligns with the broader Pro-access flow introduced in the PR.Also applies to: 18-18, 71-71
src/app/(dashboard)/generate-pro/page.tsx (1)
6-24: LGTM! Server-side Pro access enforcement.The async page component correctly:
- Validates authentication and redirects to sign-in with a return URL
- Checks Pro access via
subscriptionServicebefore rendering- Redirects to pricing with clear upgrade prompt if access is denied
- Renders
FlashcardProonly for authorized Pro usersThis server-side approach provides robust access control and aligns with the PR's goal of moving Pro checks out of middleware.
src/app/api/checkout-sessions/route.ts (1)
1-14: Approve removal of Stripe checkout endpoints
No occurrences of/api/checkout-sessionsorcheckout-sessionsfound in the codebase; safe to finalize.src/app/(auth)/sign-in/[[...sign-in]]/page.tsx (1)
12-12: LGTM! Clean post-auth redirect.The redirectUrl="/" prop ensures users land at the root after sign-in, aligning with the subscription-aware routing introduced in the broader PR.
Also applies to: 16-16
src/app/page.tsx (3)
12-13: Good integration of authentication and subscription hooks.The imports and hook usage correctly integrate Clerk's authentication with the custom subscription hook to enable entitlement-based routing.
Also applies to: 23-24
110-110: Correct CTA routing logic.The conditional routing properly directs guests to sign-up, Pro users to /generate-pro, and non-Pro authenticated users to /generate, aligning with the subscription-aware navigation introduced in this PR.
114-114: Clear and contextual CTA labels.The button labels appropriately reflect the user's authentication and subscription state, providing clear direction to users.
src/app/api/user/subscription-status/route.ts (3)
7-14: Proper authentication guard.The authentication check correctly uses Clerk v6's
await auth()pattern and returns a clear 401 response for unauthorized requests.
16-18: Clean service delegation.The route properly delegates subscription logic to the service layer and returns a clean JSON response.
20-26: Appropriate error handling.The error handling logs errors for debugging and returns a generic 500 response, avoiding information leakage.
src/components/Navbar.tsx (4)
13-15: Correct imports for Pro badge feature.The new imports support the subscription-aware UI enhancements, including the Pro badge display.
22-22: Clean hook integration.The subscription hook is properly integrated to access Pro status and loading state.
153-159: Well-guarded Pro badge display.The loading check prevents badge flashing, and the canAccessPro guard ensures accurate Pro status display.
126-126: Consistent subscription-aware navigation.Desktop and mobile CTAs correctly route based on authentication and subscription state, maintaining consistency across the navbar.
Also applies to: 130-130, 260-260, 264-264
src/hooks/useSubscription.ts (3)
15-24: Safe default state initialization.The hook initializes with secure defaults (free plan, no Pro access) and loading: true to handle the initial fetch state.
26-33: Proper effect dependencies and guards.The useEffect correctly waits for Clerk's isLoaded state and handles the unauthenticated case by setting loading to false.
35-61: Robust fetch and refresh logic.The fetch function includes proper error handling and response validation. The refreshSubscription helper correctly manages loading state during re-fetches.
src/app/api/generate/route.ts (4)
10-15: Correct authentication pattern.The authentication guard properly uses Clerk v6's async auth() and returns a clear 401 for unauthorized requests.
17-22: Thorough input validation.The input validation correctly checks for missing or empty text content, including whitespace-only inputs.
24-34: Well-defined plan-based rate limits.The tiered monthly caps are reasonable and properly fetched from the subscription service. The fallback to the free tier (10) provides a safe default.
36-54: Robust monthly usage tracking and limit enforcement.The UTC-based month calculation ensures consistency across timezones, and the usage query correctly counts flashcards created this month. The 403 response with descriptive error message provides clear feedback when limits are reached.
src/app/pricing/page.tsx (4)
15-16: Good Suspense integration for useSearchParams.The component split into PricingContent and Page wrapper with Suspense correctly resolves the Next.js warning about useSearchParams usage. This aligns with the PR objectives stating "suspense warning on /pricing resolved."
Also applies to: 42-42, 600-606
45-45: Clear upgrade flow feedback.The upgrade detection and toast notification provide clear user feedback when users are redirected to pricing due to Pro access requirements, improving the UX of the subscription flow.
Also applies to: 53-64
170-175: Flexible post-payment redirect.The conditional redirect allows users to return to their original page after upgrading (when redirectUrl is provided) or defaults to the result page, providing a smooth payment flow.
50-50: Comprehensive toast-based feedback.The toast notifications throughout the payment flow provide clear, user-friendly feedback for all payment states (setup, success, verification, errors, cancellation), improving the UX over traditional alerts.
Also applies to: 58-64, 98-101, 137-140, 162-166, 182-186, 196-200, 219-223, 235-239
src/middleware.ts (2)
10-11: Correct public route configuration.Adding
/api/razorpay/webhookto public routes enables external Razorpay callbacks, and removing/generate-profrom public routes aligns with the Pro access gating strategy.
18-18: Clear architectural documentation.The comments clearly document that Pro access validation is now handled at the page component level due to Prisma Edge Runtime constraints, providing helpful context for future maintainers.
Also applies to: 26-27
src/components/core/generate.tsx (6)
34-34: LGTM! Submission state tracking added.The
isSubmittingstate effectively prevents race conditions and provides the foundation for disabling the UI during generation.
40-41: LGTM! Concurrent submission guard in place.The early return when already submitting is a good defensive measure. Since the button is disabled during submission (line 101), users receive visual feedback, making the silent early return acceptable.
45-45: LGTM! Content-Type header matches body format.Setting
Content-Type: text/plainis correct when sending the text string directly in the request body.
49-56: LGTM! Improved error handling with graceful fallback.The error handling properly attempts to parse API error details (lines 51-54) and falls back to a default message if parsing fails. The empty catch block is acceptable here since a fallback message is already in place.
101-103: LGTM! Proper button state management.The button correctly disables during submission and displays a loading message, providing clear feedback to users.
58-63: Destructuring matches API response shape: the handler returns{ flashcards: Flashcard[] }, soconst { flashcards: generated } = await response.json()is correct.
There was a problem hiding this 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
♻️ Duplicate comments (2)
src/components/Footer.tsx (1)
102-103: Routing logic now aligns with PR objectives.The conditional routing correctly implements the stated behavior:
- Non-signed-in users →
/sign-up- Signed-in Free users →
/generate- Signed-in Pro users →
/generate-proThis resolves the routing inconsistency flagged in the previous review.
src/components/core/generate.tsx (1)
68-68: Fix invalid catch clause type annotation.TypeScript and Biome require catch clause variables to be typed as
any,unknown, or have the type annotation omitted. The union typeError | unknownis invalid becauseunknownis already the top type.Apply this diff to fix the type annotation:
- } catch (error: Error | unknown) { + } catch (error: unknown) { console.error('Error generating flashcards:', error) toast({ title: "Error", description: error instanceof Error ? error.message : 'An unexpected error occurred', variant: "destructive", });Note: The runtime handling at line 72 is already correct with the
instanceof Errorcheck.
🧹 Nitpick comments (1)
src/app/api/razorpay/webhook/route.ts (1)
43-62: Validate event payload shape (schema) before dispatch.Guard against undefined payload structure and reduce runtime errors. Use a minimal zod schema or narrow by in-operator checks before accessing nested fields.
I can provide a small zod schema for payment/order events if helpful.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/app/api/razorpay/webhook/route.ts(1 hunks)src/components/Footer.tsx(2 hunks)src/components/core/generate.tsx(2 hunks)src/hooks/useSubscription.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/hooks/useSubscription.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/api/razorpay/webhook/route.ts (1)
src/lib/database.ts (1)
prisma(7-9)
src/components/Footer.tsx (1)
src/hooks/useSubscription.ts (1)
useSubscription(15-63)
🪛 Biome (2.1.2)
src/components/core/generate.tsx
[error] 68-68: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
(parse)
🔇 Additional comments (8)
src/components/Footer.tsx (1)
7-13: LGTM! Auth and subscription state integration is correct.The addition of
useUseranduseSubscriptionhooks properly provides the authentication and entitlement state needed for conditional routing. ThecanAccessProdefaults tofalseduring loading, which safely falls back to non-pro paths until the subscription status is fetched.src/components/core/generate.tsx (3)
34-34: LGTM! Submission state management prevents double submissions.The
isSubmittingstate with guard logic and finally block correctly prevents concurrent submissions while ensuring the button state is always reset. The disabled button with "Generating..." label provides clear user feedback.Also applies to: 40-41, 75-77, 101-102
45-45: LGTM! Content-Type aligns with plain text body.Setting
Content-Type: text/plainis correct sincedata.textis sent directly as the request body without JSON serialization.
50-56: LGTM! Enhanced error handling and response validation.The improvements provide better user feedback:
- Attempts to parse error details from JSON responses with graceful fallback
- Validates flashcards data structure before updating state
- Shows flashcard count in success toast
Also applies to: 58-66
src/app/api/razorpay/webhook/route.ts (4)
12-21: Good: runtime flags and env check are correct for Next App Router.force-dynamic + nodejs is appropriate here, and checking the secret inside the handler avoids build-time failures. LGTM.
56-59: Clarify order.paid responsibilities vs payment.captured.Currently order.paid only flips payment statuses, while activation happens on payment.captured. Confirm Razorpay’s delivery order guarantees so users aren’t left unactivated if only order.paid arrives.
If needed, enrich order.paid to perform activation when a matching, not-yet-activated payment exists (idempotently).
Also applies to: 166-184
22-28: Optional: confirm Razorpay retry policy for 4xx.If Razorpay retries on non-2xx, 400 for invalid/missing signature may cause repeated calls. Confirm expected behavior and adjust (e.g., 200 with logged rejection) if necessary to avoid webhook spam.
114-121: [running verification]#!/bin/bash # Locate payment assignment and Razorpay usage in route.ts rg -nP "const\s+payment\s*=" -C3 src/app/api/razorpay/webhook/route.ts rg -n "payment.id" -C3 src/app/api/razorpay/webhook/route.ts rg -n "new Razorpay" -C3 src/app/api/razorpay/webhook/route.ts rg -n "import .*razorpay" -C3 src/app/api/razorpay/webhook/route.ts
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@Suraj-kumar00 have you reviewed my PR ? |
|
Hi @Vanshika814, sorry for the delay. Could you please add the section so that user can find to navigate to the pro subscription. You how the other platform give option to upgrade and then redirect to the pricing and when the payment is done in the user's profile it show whatever plan they have purchased right! So one more thing I want to ask that how the payment subscription is being handled, like will fake and real payment work or only the real payment will work and if so what I have to do from my end like adding the secrets or what ever steps are there please let me know. Let me know if you can close this tomorrow so that I can merge this PR ASAP. Thanks for your contribution @Vanshika814 ... |
|
Also do follow the suggestion of the coderabbitai. |
yeah sure i'll try to make all the changes by today. |
for this what we can do is the pricing page is already there, so for the users we can change it's name to upgrade to pro or something like this. and then redirect the user to pricing page. |
real payments are being handled Steps to setup:
|
…and update payment record lookup to include fallback by order ID
|
Hey @Vanshika814 everything looks good to me... merging the PR. Thanks again for contributing to FlashFathomAI. |
Title
Razorpay webhooks, Pro access control, dynamic CTAs, monthly limits, and stability fixes
Summary
This PR implements secure payments, subscription gating, and UX improvements, and hardens flashcard generation reliability.
Key changes
Payments and subscriptions
Access control and routing
Gemini generation stability
UI/UX
Testing
Migration/Env
Fixes #96
Summary by CodeRabbit
New Features
Changes
Chores