Add Stripe (Next.js/TypeScript) and Anthropic Claude API rules - #345
Add Stripe (Next.js/TypeScript) and Anthropic Claude API rules#345moukie76 wants to merge 1 commit into
Conversation
…ctories entry - rules/stripe-integration-nextjs-typescript.mdc: secure checkout sessions, idempotent webhook verification, subscription lifecycle, customer portal - rules/anthropic-claude-api-typescript.mdc: streaming, tool use, model selection guide, error handling with backoff - README.md: add both rules to appropriate sections + Cursor Rules pack to Directories
📝 WalkthroughWalkthroughThe PR adds Anthropic Claude and Stripe Cursor rule documents for TypeScript/Next.js integrations, covering API usage, streaming, tools, checkout, webhooks, and portal sessions. README entries catalog both rules and a multi-stack configuration bundle. ChangesAnthropic Claude rule
Stripe integration rule
Rules bundle catalog
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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.
Actionable comments posted: 11
🧹 Nitpick comments (4)
rules/anthropic-claude-api-typescript.mdc (3)
42-42: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winReuse the module-level client in the Edge example.
This creates a new client per request, contradicting the singleton guidance at Lines 13–16 and Line 110. Move the client to module scope and import/reuse it here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rules/anthropic-claude-api-typescript.mdc` at line 42, Update the Edge example to remove the per-request Anthropic instantiation and reuse the module-level client defined by the singleton guidance. Move or import that client at module scope, then have the request handling path reference it directly while preserving the existing API key configuration.
3-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNarrow the rule glob.
**/*.tsapplies this Anthropic-specific rule to every TypeScript file, including unrelated application code. Restrict the glob to Anthropic/client and API-route paths, or rely on manual application, to avoid unrelated rules being injected into other files.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rules/anthropic-claude-api-typescript.mdc` at line 3, Update the rule frontmatter glob configuration to remove the broad **/*.ts pattern and limit matching to Anthropic/client files and relevant API chat route paths, preventing this Anthropic-specific guidance from applying to unrelated TypeScript files.
59-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winShow the tool-result round trip.
This example only declares
get_weatherand sends the initial request. If Claude emitstool_use, the caller must execute the tool and send a matchingtool_resultmessage; otherwise the example stops without producing a final answer. Include the continuation or explicitly state that this snippet ends at the tool call. The SDK also providestoolRunner()for this flow. (platform.claude.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rules/anthropic-claude-api-typescript.mdc` around lines 59 - 74, Extend the example after anthropic.messages.create to handle a tool_use response: execute get_weather, send the matching tool_result in a follow-up messages.create request, and display the final response. Alternatively, explicitly state that the snippet intentionally ends at the tool call, but ensure the example documents the required continuation or references the SDK toolRunner() flow.rules/stripe-integration-nextjs-typescript.mdc (1)
67-80: 🩺 Stability & Availability | 🔵 TrivialKeep heavy webhook work off the request path.
The example awaits both business handlers before responding. If they perform database or network work, enqueue the verified, deduplicated event and return
2xx; otherwise delivery spikes can increase latency and retries. (docs.stripe.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rules/stripe-integration-nextjs-typescript.mdc` around lines 67 - 80, Update the webhook switch around handleCheckoutComplete and handleSubscriptionCancelled so verified, deduplicated events are enqueued for asynchronous processing instead of awaiting business handlers during the request. Return the successful 2xx Response immediately after enqueueing, while preserving the existing event-type handling and payload mapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@README.md`:
- Line 327: Update the “Cursor Rules & AI Coding Config Pack” entry in the
README to use the linked page’s current published count of 25 production-ready
rule files, keeping the existing link and stack description unchanged.
In `@rules/anthropic-claude-api-typescript.mdc`:
- Line 109: Update the max_tokens guidance in the Anthropic Claude API rules to
describe it as the caller-selected maximum output-token ceiling, not a default
that silently truncates responses. Instruct callers to choose an appropriate
limit and handle stop_reason: "max_tokens" when the response reaches that cap.
- Around line 103-106: Update the Model Selection table to reflect Anthropic’s
current lineup by adding Claude Fable 5 as the highest-capability recommendation
and Opus 5 and Sonnet 5 as current alternatives, or explicitly label the
existing claude-opus-4-6 and claude-sonnet-4-6 entries as compatibility choices
while preserving their valid IDs.
- Around line 40-49: Add validation immediately after req.json() and before
constructing Anthropic or calling messages.stream: require an authenticated,
rate-limited request and validate messages against a schema with permitted roles
and content types. Enforce maximum message count, content size, and any total
payload/history limits, returning an appropriate client error for invalid or
excessive input; only forward validated, bounded messages to Claude.
- Around line 44-53: Update the streaming example around
anthropic.messages.stream and stream.toReadableStream to handle failures emitted
after the HTTP 200 response by exposing an SSE error event and aborting or
closing the stream with an application-level fallback. Document that retries are
safe only before any output has been delivered, while preserving the existing
successful streaming response behavior.
- Around line 95-98: Replace the invalid Anthropic.APIStatusError check with the
TypeScript SDK’s Anthropic.APIError (or its documented status-code subclasses)
in the 429/529 handling logic, preserving the existing rate-limit and overload
retry behavior.
In `@rules/stripe-integration-nextjs-typescript.mdc`:
- Around line 49-61: Update the standalone POST webhook example to import the
Stripe namespace/type and the configured stripe client used by constructEvent.
Also resolve handleCheckoutComplete and handleSubscriptionCancelled by importing
them from their actual module or declaring clearly typed placeholder handlers,
so the complete route compiles without undefined symbols.
- Around line 3-14: Restrict the rule’s globs to Stripe server modules and route
handlers, removing the broad **/*.ts pattern that covers client and unrelated
browser code. In the Stripe client setup for lib/stripe.ts, add the server-only
import before creating the client so modules using STRIPE_SECRET_KEY cannot be
imported into client code.
- Around line 22-43: Update createCheckoutSession to authenticate the caller,
derive and authorize userId server-side, map plan selections to server-owned
Stripe prices instead of trusting arbitrary priceId values, and allowlist
same-origin success and cancel URLs. At
rules/stripe-integration-nextjs-typescript.mdc lines 86-90, apply equivalent
customerId derivation/authorization and returnUrl validation to the portal
helper; at lines 102-107, document that webhook verification authenticates
Stripe events but does not replace server-side price or entitlement validation.
- Around line 67-76: Update the webhook flow before the event.type switch to
atomically claim and persist event.id after signature verification. Return HTTP
200 immediately when the event was already claimed or processed, and retain the
claim through dispatch so handler failures leave the event eligible for retry
rather than marking it successfully completed.
- Around line 50-54: Update the POST function to await headers() and store the
resolved headers in requestHeaders before reading the stripe-signature value;
then call .get on requestHeaders while preserving the existing request body and
signature flow.
---
Nitpick comments:
In `@rules/anthropic-claude-api-typescript.mdc`:
- Line 42: Update the Edge example to remove the per-request Anthropic
instantiation and reuse the module-level client defined by the singleton
guidance. Move or import that client at module scope, then have the request
handling path reference it directly while preserving the existing API key
configuration.
- Line 3: Update the rule frontmatter glob configuration to remove the broad
**/*.ts pattern and limit matching to Anthropic/client files and relevant API
chat route paths, preventing this Anthropic-specific guidance from applying to
unrelated TypeScript files.
- Around line 59-74: Extend the example after anthropic.messages.create to
handle a tool_use response: execute get_weather, send the matching tool_result
in a follow-up messages.create request, and display the final response.
Alternatively, explicitly state that the snippet intentionally ends at the tool
call, but ensure the example documents the required continuation or references
the SDK toolRunner() flow.
In `@rules/stripe-integration-nextjs-typescript.mdc`:
- Around line 67-80: Update the webhook switch around handleCheckoutComplete and
handleSubscriptionCancelled so verified, deduplicated events are enqueued for
asynchronous processing instead of awaiting business handlers during the
request. Return the successful 2xx Response immediately after enqueueing, while
preserving the existing event-type handling and payload mapping.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f67bf586-9e5a-45e7-b8a0-88172115d064
📒 Files selected for processing (3)
README.mdrules/anthropic-claude-api-typescript.mdcrules/stripe-integration-nextjs-typescript.mdc
|
|
||
| - [CursorList](https://cursorlist.com) | ||
| - [CursorDirectory](https://cursor.directory/) | ||
| - [Cursor Rules & AI Coding Config Pack](https://moukie76.github.io/cursor-rules-free/) - 27 production-ready rules for 16 stacks: Next.js, React, Python FastAPI, Go, Stripe, Anthropic Claude, Tailwind, Docker, and more. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the linked pack’s published count.
The README says “27 production-ready rules,” while the linked page currently advertises “25 production-ready rule files.” Update the README or the linked page so the catalog does not publish conflicting counts. (moukie76.github.io)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` at line 327, Update the “Cursor Rules & AI Coding Config Pack”
entry in the README to use the linked page’s current published count of 25
production-ready rule files, keeping the existing link and stack description
unchanged.
| const { messages } = await req.json() | ||
|
|
||
| const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }) | ||
|
|
||
| const stream = anthropic.messages.stream({ | ||
| model: "claude-opus-4-6", | ||
| max_tokens: 2048, | ||
| system: "You are a helpful assistant.", | ||
| messages, | ||
| }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Validate and bound the incoming messages before forwarding them.
req.json() is passed directly to a paid upstream API with no schema, role/content, size, authentication, or rate-limit checks. A public route can therefore generate malformed requests or incur unbounded token spend. Add request validation and enforce payload/history limits before calling Claude.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/anthropic-claude-api-typescript.mdc` around lines 40 - 49, Add
validation immediately after req.json() and before constructing Anthropic or
calling messages.stream: require an authenticated, rate-limited request and
validate messages against a schema with permitted roles and content types.
Enforce maximum message count, content size, and any total payload/history
limits, returning an appropriate client error for invalid or excessive input;
only forward validated, bounded messages to Claude.
| const stream = anthropic.messages.stream({ | ||
| model: "claude-opus-4-6", | ||
| max_tokens: 2048, | ||
| system: "You are a helpful assistant.", | ||
| messages, | ||
| }) | ||
|
|
||
| return new Response(stream.toReadableStream(), { | ||
| headers: { "Content-Type": "text/event-stream" }, | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Document mid-stream failure handling.
A try/catch around stream creation cannot catch errors emitted after the HTTP 200 response, and toReadableStream() forwards those failures to the consumer without an application-level error event or fallback. Anthropic documents this as a distinct streaming failure mode; add an SSE error/abort strategy and clarify that retries are only safe before output has been delivered. (platform.claude.com)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/anthropic-claude-api-typescript.mdc` around lines 44 - 53, Update the
streaming example around anthropic.messages.stream and stream.toReadableStream
to handle failures emitted after the HTTP 200 response by exposing an SSE error
event and aborting or closing the stream with an application-level fallback.
Document that retries are safe only before any output has been delivered, while
preserving the existing successful streaming response behavior.
| if (err instanceof Anthropic.APIStatusError) { | ||
| if (err.status === 429) { /* rate limited — backoff and retry */ } | ||
| if (err.status === 529) { /* overloaded — retry with exponential backoff */ } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n '"`@anthropic-ai/sdk`"|APIStatusError|APIError' \
--glob 'package.json' \
--glob '*lock*' \
--glob '!node_modules/**'Repository: PatrickJS/awesome-cursorrules
Length of output: 167
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Candidate file lines 80-110:"
sed -n '80,110p' rules/anthropic-claude-api-typescript.mdc || true
echo
echo "Search for Anthropic error class mentions in tracked files:"
rg -n 'Anthropic\.(APIStatusError|APIError)' . --glob '!node_modules/**' || true
echo
echo "Lock/package references:"
for f in package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lockb bun.lock npm-shrinkwrap.json; do
[ -f "$f" ] && echo "--- $f ---" && rg -n '"?`@anthropic-ai/sdk`"?|APIStatusError|APIError' "$f" || true
doneRepository: PatrickJS/awesome-cursorrules
Length of output: 1210
🌐 Web query:
Anthropic TypeScript SDK APIError APIStatusError @anthropic-ai/sdk``
💡 Result:
In the Anthropic TypeScript SDK (@anthropic-ai/sdk), APIError is the base class for errors returned by the API [1][2]. All errors in the SDK inherit from a base AnthropicError class [1]. While the Python SDK explicitly uses an APIStatusError class for non-success status codes [3][4], the TypeScript SDK primarily surfaces specific subclasses of APIError (such as BadRequestError, RateLimitError, and InternalServerError) to handle 4xx and 5xx responses [1][2][5]. Key Error Handling Details: 1. Hierarchy and Subclasses: When the API returns a non-success status code (4xx or 5xx), the SDK throws a specific subclass of APIError [5]. Common subclasses include [2][5]: - BadRequestError (400) - AuthenticationError (401) - PermissionDeniedError (403) - NotFoundError (404) - ConflictError (409) - UnprocessableEntityError (422) - RateLimitError (429) - InternalServerError (>=500) 2. Connection Errors: If the library cannot connect to the API or encounters a network issue, it throws an APIConnectionError or APIConnectionTimeoutError, which are also treated as APIError types [1][2][5]. 3. Inspecting Errors: You can catch these errors and inspect properties such as status, headers, and the raw error body [1][5]. The APIError instance includes the request-id header, which is essential for debugging and contacting support [1]. Example usage: const message = await client.messages.create({...}).catch((err) => { if (err instanceof Anthropic.APIError) { console.log(err.status); // e.g., 400 console.log(err.requestID); // Request ID for debugging } }); Note: If you encounter references to APIStatusError, please be aware that this is a core component of the Python SDK's error hierarchy [3][4] and is generally not exposed as a primary class in the TypeScript SDK's public API [2][5]. Always refer to the specific status-code-based subclasses when writing TypeScript error handling logic [5].
Citations:
- 1: https://deepwiki.com/anthropics/anthropic-sdk-typescript/2.4-error-handling
- 2: https://github.com/anthropics/anthropic-sdk-typescript/blob/main/src/index.ts
- 3: https://github.com/anthropics/anthropic-sdk-python/blob/3955600d/README.md
- 4: https://deepwiki.com/anthropics/anthropic-sdk-python/10.2-exception-types-and-error-handling
- 5: https://platform.claude.com/docs/en/cli-sdks-libraries/sdks/typescript
🌐 Web query:
@anthropic-ai/sdk 1.4.0 APIStatusError APIError
💡 Result:
In the Anthropic TypeScript SDK (@anthropic-ai/sdk), APIError is the base class for all errors returned by the API [1]. While you mentioned APIStatusError, that specific class name is used in the Anthropic Python SDK [2][3][4] to handle 4xx and 5xx status codes, whereas the TypeScript SDK primarily uses the APIError class hierarchy to represent these errors [1][5]. In the TypeScript SDK: 1. APIError is the base class for errors that occur during API communication [1]. It includes properties such as status, message, headers, and the raw error body [6][1]. 2. Subclasses of APIError are used to represent specific HTTP status codes, such as BadRequestError (400), AuthenticationError (401), PermissionDeniedError (403), RateLimitError (429), and InternalServerError (>=500) [5][7]. 3. If you are catching errors, you should check against the base APIError class [5][7]. Because APIError is a common base, you can inspect the err.status or err.name properties on the caught error object to differentiate between specific failure types [5][7]. If you are seeing APIStatusError in documentation or code, ensure you are referencing the correct language-specific SDK documentation, as the exception naming conventions differ between the TypeScript and Python implementations [2][5]. For the TypeScript SDK, the authoritative reference for error handling is available in the official Claude documentation [7].
Citations:
- 1: https://deepwiki.com/anthropics/anthropic-sdk-typescript/2.4-error-handling
- 2: https://github.com/anthropics/anthropic-sdk-python/blob/49d639a6/src/anthropic/_exceptions.py
- 3: https://github.com/anthropics/anthropic-sdk-python/blob/3955600d/src/anthropic/_exceptions.py
- 4: https://github.com/anthropics/anthropic-sdk-python/blob/04b468da/src/anthropic/_exceptions.py
- 5: https://github.com/anthropics/anthropic-sdk-typescript/blob/ccf4cac31381624d965ba32ebb22c1db0b207248/README.md
- 6: https://github.com/anthropics/anthropic-sdk-typescript/blob/0f8153b3/src/core/error.ts
- 7: https://platform.claude.com/docs/en/cli-sdks-libraries/sdks/typescript
🌐 Web query:
site:github.com/anthropics/anthropic-sdk-typescript APIStatusError APIError
💡 Result:
In the context of the Anthropic TypeScript/JavaScript SDK, APIError is a primary base class for errors thrown when the API returns a non-success status code (4xx or 5xx) [1]. APIStatusError is an internal method, makeStatusError, used by the client to generate these specific error instances based on the HTTP status code received [2]. Key points: - APIError: This is the parent class for all errors related to failed API requests [3][1]. When an API call returns a 4xx or 5xx status code, the SDK throws a subclass of APIError (such as BadRequestError, AuthenticationError, or RateLimitError) [1]. - makeStatusError (Internal): This is an internal client method (e.g., Anthropic.makeStatusError) that inspects the HTTP response and instantiates the appropriate APIError subclass [2]. - Error Handling: Developers should generally catch APIError to handle various API-related issues [1]. For example: const client = new Anthropic; try { await client.messages.create(...); } catch (err) { if (err instanceof Anthropic.APIError) { console.log(err.status); // HTTP status code (e.g., 400, 401) console.log(err.name); // Specific error name (e.g., AuthenticationError) } } [1]
Citations:
- 1: https://github.com/anthropics/anthropic-sdk-typescript/blob/ccf4cac31381624d965ba32ebb22c1db0b207248/README.md
- 2:
messages.stream().withResponse()throws uncatchable errors anthropics/anthropic-sdk-typescript#829 - 3: https://github.com/anthropics/anthropic-sdk-typescript/blob/main/src/index.ts
🌐 Web query:
platform.claude.com TypeScript SDK APIError APIStatusError
💡 Result:
In the Anthropic TypeScript SDK (@anthropic-ai/sdk), APIError is the base class for all errors returned by the API [1][2]. When the SDK encounters a non-success HTTP status code (4xx or 5xx) or a connection issue, it throws a specific subclass of APIError [1][3]. APIStatusError is not a standard top-level class exported in the TypeScript SDK's public API documentation, unlike in the Python SDK where it serves as a base for status-code-related exceptions [4]. In the TypeScript SDK, errors are generally categorized into APIError and its subclasses [1][5]. Key error subclasses include: - BadRequestError (400) [1][3] - AuthenticationError (401) [1][3] - PermissionDeniedError (403) [1][3] - NotFoundError (404) [1][3] - ConflictError (409) [1][3] - UnprocessableEntityError (422) [1][3] - RateLimitError (429) [1][3] - InternalServerError (>=500) [1][3] - APIConnectionError (N/A, typically network-related) [1][3] These errors allow you to catch and handle specific failure conditions effectively [1][6]. The APIError objects provide access to metadata such as the HTTP status code (err.status), the error name (err.name), headers, and the request ID, which is essential for debugging [1][3][7]. The SDK also supports automatic retries for certain error types (e.g., 408, 429, and 5xx errors), which can be configured via the maxRetries option [3].
Citations:
- 1: https://platform.claude.com/docs/en/cli-sdks-libraries/sdks/typescript
- 2: https://deepwiki.com/anthropics/anthropic-sdk-typescript/2.4-error-handling
- 3: https://github.com/anthropics/anthropic-sdk-typescript/blob/21ad14ff1f0c0930f3b99b6e03167bf9d57fbd9d/README.md
- 4: https://github.com/anthropics/anthropic-sdk-python/blob/d2f6543e/src/anthropic/_exceptions.py
- 5: https://app.unpkg.com/@anthropic-ai/sdk@0.112.4/files/core/error.mjs
- 6: https://platform.claude.com/docs/en/api/errors
- 7: https://cdn.jsdelivr.net/npm/@anthropic-ai/sdk@0.52.0/src/core/error.ts
🌐 Web query:
site:platform.claude.com/docs/en/cli-sdks-libraries/sdks/typescript APIError APIStatusError
💡 Result:
APIError is a base exception class used by certain software development kits (SDKs), such as the Anthropic TypeScript SDK, to handle errors that occur when interacting with an API [1]. When an SDK is unable to successfully connect to an API or receives a non-success HTTP status code (typically 4xx or 5xx ranges), it throws a subclass of APIError [1]. These subclasses allow developers to inspect details about the failure, such as the status code, the specific error type (e.g., BadRequestError, AuthenticationError, or RateLimitError), and response headers [1]. While APIError is a standard convention in several client libraries for representing API-related failures, APIStatusError is not a widely documented or standard term across major SDKs based on available information. It is possible that APIStatusError refers to a library-specific error naming convention or a custom implementation in a codebase not covered by general documentation [1]. Example usage for handling APIError in TypeScript: const message = await client.messages.create({... }).catch(async (err) => { if (err instanceof Anthropic.APIError) { console.log(err.status); console.log(err.name); } else { throw err; } });
Citations:
Use the TypeScript SDK’s APIError class.
Anthropic.APIStatusError is a Python SDK error name; the documented TypeScript SDK exposes Anthropic.APIError and its status-code subclasses. This copied snippet can fail TypeScript checking before handling 429/529 responses.
Proposed fix
- if (err instanceof Anthropic.APIStatusError) {
+ if (err instanceof Anthropic.APIError) {📝 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.
| if (err instanceof Anthropic.APIStatusError) { | |
| if (err.status === 429) { /* rate limited — backoff and retry */ } | |
| if (err.status === 529) { /* overloaded — retry with exponential backoff */ } | |
| } | |
| if (err instanceof Anthropic.APIError) { | |
| if (err.status === 429) { /* rate limited — backoff and retry */ } | |
| if (err.status === 529) { /* overloaded — retry with exponential backoff */ } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/anthropic-claude-api-typescript.mdc` around lines 95 - 98, Replace the
invalid Anthropic.APIStatusError check with the TypeScript SDK’s
Anthropic.APIError (or its documented status-code subclasses) in the 429/529
handling logic, preserving the existing rate-limit and overload retry behavior.
| ## Model Selection | ||
| - claude-haiku-4-5-20251001: fast and cheap, good for classification, short tasks | ||
| - claude-sonnet-4-6: balanced speed/intelligence for most tasks | ||
| - claude-opus-4-6: maximum intelligence for complex reasoning |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Refresh the model-selection recommendations.
These IDs remain valid, but the descriptions are stale for the current model lineup: Anthropic now lists Claude Fable 5 as the highest-capability model, with Opus 5 and Sonnet 5 as current alternatives. Either update this table or label the 4.6 models as compatibility choices. (platform.claude.com)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/anthropic-claude-api-typescript.mdc` around lines 103 - 106, Update the
Model Selection table to reflect Anthropic’s current lineup by adding Claude
Fable 5 as the highest-capability recommendation and Opus 5 and Sonnet 5 as
current alternatives, or explicitly label the existing claude-opus-4-6 and
claude-sonnet-4-6 entries as compatibility choices while preserving their valid
IDs.
| globs: **/stripe.ts, **/webhook/route.ts, **/checkout/route.ts, **/*.ts | ||
| alwaysApply: false | ||
| --- | ||
|
|
||
| # Stripe Integration — Next.js TypeScript | ||
|
|
||
| ## Client Setup | ||
| ```ts | ||
| // lib/stripe.ts | ||
| import Stripe from "stripe" | ||
|
|
||
| export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Narrow the rule scope and enforce a server-only Stripe client.
**/*.ts applies this rule to client components and unrelated browser code, while lib/stripe.ts reads STRIPE_SECRET_KEY without an explicit server-only boundary. Restrict the glob to server modules/routes and add import "server-only" so the secret-bearing client cannot be imported into client code. This matches the repository’s Stripe secret-handling invariant from rules/nextjs15-supabase-cursorrules-prompt-file.mdc.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/stripe-integration-nextjs-typescript.mdc` around lines 3 - 14, Restrict
the rule’s globs to Stripe server modules and route handlers, removing the broad
**/*.ts pattern that covers client and unrelated browser code. In the Stripe
client setup for lib/stripe.ts, add the server-only import before creating the
client so modules using STRIPE_SECRET_KEY cannot be imported into client code.
| export async function createCheckoutSession({ | ||
| priceId, | ||
| userId, | ||
| successUrl, | ||
| cancelUrl, | ||
| }: { | ||
| priceId: string | ||
| userId: string | ||
| successUrl: string | ||
| cancelUrl: string | ||
| }) { | ||
| return stripe.checkout.sessions.create({ | ||
| mode: "subscription", | ||
| payment_method_types: ["card"], | ||
| line_items: [{ price: priceId, quantity: 1 }], | ||
| success_url: successUrl, | ||
| cancel_url: cancelUrl, | ||
| metadata: { userId }, | ||
| subscription_data: { | ||
| metadata: { userId }, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Validate caller-controlled Stripe identifiers and URLs on the server.
The checkout and portal helpers accept arbitrary priceId, userId, customerId, and redirect URLs. If these originate from request data, callers can select unintended prices, attach metadata to another account, or redirect users to attacker-controlled origins. Derive identities from authentication, map plan names to server-owned prices, and allowlist same-origin redirect URLs. Also clarify that webhook verification authenticates the Stripe event; it does not replace server-side price or entitlement validation. (docs.stripe.com)
rules/stripe-integration-nextjs-typescript.mdc#L22-L43: validate/authenticate checkout inputs before creating the session.rules/stripe-integration-nextjs-typescript.mdc#L86-L90: derive and authorizecustomerId, and validatereturnUrl.rules/stripe-integration-nextjs-typescript.mdc#L102-L107: document these server-side authorization and allowlisting requirements.
📍 Affects 1 file
rules/stripe-integration-nextjs-typescript.mdc#L22-L43(this comment)rules/stripe-integration-nextjs-typescript.mdc#L86-L90rules/stripe-integration-nextjs-typescript.mdc#L102-L107
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/stripe-integration-nextjs-typescript.mdc` around lines 22 - 43, Update
createCheckoutSession to authenticate the caller, derive and authorize userId
server-side, map plan selections to server-owned Stripe prices instead of
trusting arbitrary priceId values, and allowlist same-origin success and cancel
URLs. At rules/stripe-integration-nextjs-typescript.mdc lines 86-90, apply
equivalent customerId derivation/authorization and returnUrl validation to the
portal helper; at lines 102-107, document that webhook verification
authenticates Stripe events but does not replace server-side price or
entitlement validation.
| // app/api/stripe/webhook/route.ts | ||
| import { headers } from "next/headers" | ||
|
|
||
| export async function POST(req: Request) { | ||
| const body = await req.text() | ||
| const signature = headers().get("stripe-signature")! | ||
|
|
||
| let event: Stripe.Event | ||
| try { | ||
| event = stripe.webhooks.constructEvent( | ||
| body, | ||
| signature, | ||
| process.env.STRIPE_WEBHOOK_SECRET! |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make the webhook example compile as a standalone route.
The route uses Stripe.Event and stripe without importing either symbol. handleCheckoutComplete and handleSubscriptionCancelled are also undefined in the provided example; import them or mark them explicitly as placeholders with signatures.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/stripe-integration-nextjs-typescript.mdc` around lines 49 - 61, Update
the standalone POST webhook example to import the Stripe namespace/type and the
configured stripe client used by constructEvent. Also resolve
handleCheckoutComplete and handleSubscriptionCancelled by importing them from
their actual module or declaring clearly typed placeholder handlers, so the
complete route compiles without undefined symbols.
| import { headers } from "next/headers" | ||
|
|
||
| export async function POST(req: Request) { | ||
| const body = await req.text() | ||
| const signature = headers().get("stripe-signature")! |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- candidate file excerpt ---\n'
sed -n '1,120p' rules/stripe-integration-nextjs-typescript.mdc || true
printf '\n--- related files mentioning next/headers ---\n'
rg -n 'from ["\x27]next/headers["\x27]|headers\(\)|stripe-signature' -S . || true
printf '\n--- package/config hints ---\n'
for f in package.json tsconfig.json next.config.js next.config.mjs app.config.js app.config.mjs; do
[ -f "$f" ] && { echo "### $f"; sed -n '1,220p' "$f"; }
doneRepository: PatrickJS/awesome-cursorrules
Length of output: 258
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate file excerpt ---'
sed -n '1,120p' rules/stripe-integration-nextjs-typescript.mdc || true
printf '%s\n' ''
printf '%s\n' '--- related files mentioning next/headers ---'
rg -n 'from ["'"'"']next/headers["'"'"']|headers\(\)|stripe-signature' -S . || true
printf '%s\n' ''
printf '%s\n' '--- package/config hints ---'
for f in package.json tsconfig.json next.config.js next.config.mjs app.config.js app.config.mjs; do
[ -f "$f" ] && { echo "### $f"; sed -n '1,220p' "$f"; }
doneRepository: PatrickJS/awesome-cursorrules
Length of output: 4586
🌐 Web query:
Next.js next/headers headers() function async returns Promise RequestHeaders documentation
💡 Result:
In Next.js 15 and later, the headers() function from next/headers is an asynchronous function that returns a Promise [1][2]. This change was introduced as a breaking update to better support streaming and request-time data access [3][4]. Key details regarding this change: How to Use You must now use async/await to access the headers object [1][5]: import { headers } from 'next/headers'; export default async function Page { const headersList = await headers; const userAgent = headersList.get('user-agent'); //... } In Client Components or synchronous contexts where you cannot use async/await, you can use React's use() function to unwrap the Promise [1][3]: 'use client'; import { use } from 'react'; import { headers } from 'next/headers'; function MyComponent { const headersList = use(headers); const userAgent = headersList.get('user-agent'); //... } Migration and Compatibility Next.js provides a codemod, next-async-request-api, to automatically migrate codebases to the new asynchronous pattern [3]. While synchronous access remains temporarily supported for backward compatibility, it is deprecated, will trigger warnings in development, and may be removed in future major versions [1][6][2]. Important Considerations - Dynamic Rendering: Like previous versions, using headers() opts the route segment into dynamic rendering [7][5]. - Web Standard: The function returns a read-only Web Headers object, consistent with previous versions [1][7]. - Third-Party Libraries: If a library you use relies on the synchronous headers() API, it may throw errors. You should check if the library has released a version compatible with the asynchronous requirements of newer Next.js versions [2].
Citations:
- 1: https://nextjs.org/docs/app/api-reference/functions/headers
- 2: https://nextjs.org/docs/messages/next-prerender-sync-headers
- 3: https://nextjs.org/docs/messages/sync-dynamic-apis
- 4: https://llmbestpractices.com/frontend/nextjs-async-dynamic-apis
- 5: https://vercel-next-js.mintlify.app/api-reference/functions/headers
- 6: [Breaking] Update Dynamic APIs to be async vercel/next.js#68812
- 7: https://nextjs.org/docs/14/app/api-reference/functions/headers
Await headers() before reading the signature.
This rule targets this code path, and Next.js’ App Router headers() API is async; synchronous access is deprecated compatibility behavior. Use const requestHeaders = await headers() before calling .get("stripe-signature").
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/stripe-integration-nextjs-typescript.mdc` around lines 50 - 54, Update
the POST function to await headers() and store the resolved headers in
requestHeaders before reading the stripe-signature value; then call .get on
requestHeaders while preserving the existing request body and signature flow.
| switch (event.type) { | ||
| case "checkout.session.completed": { | ||
| const session = event.data.object as Stripe.Checkout.Session | ||
| await handleCheckoutComplete(session) | ||
| break | ||
| } | ||
| case "customer.subscription.deleted": { | ||
| const sub = event.data.object as Stripe.Subscription | ||
| await handleSubscriptionCancelled(sub) | ||
| break |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)rules/stripe-integration-nextjs-typescript\.mdc$|stripe|webhook' || true
echo "== target file size and relevant sections =="
wc -l rules/stripe-integration-nextjs-typescript.mdc 2>/dev/null || true
sed -n '1,130p' rules/stripe-integration-nextjs-typescript.mdc 2>/dev/null | cat -n
echo "== related references =="
rg -n "webhook|idempotent|idempotency|Stripe\\.|handles?|checkout.session.completed|signature|retry|200|database|async_mutex|prisma|supabase|firebase" rules/stripe-integration-nextjs-typescript.mdc . --glob '!node_modules' --glob '!dist' --glob '!build' 2>/dev/null | head -200Repository: PatrickJS/awesome-cursorrules
Length of output: 31364
🌐 Web query:
Stripe webhook duplicate events retry deduplication idempotency node documentation
💡 Result:
To handle duplicate Stripe webhook events in a Node.js integration, you must implement idempotency logic to ensure your application handles the same event only once, even if Stripe delivers it multiple times [1][2][3]. Key Concepts and Strategy: 1. Use the Event ID as the Deduplication Key: Every Stripe webhook event includes a unique ID (e.g., evt_123) in the payload [1][3]. This ID remains consistent across all retry attempts, making it the correct key for deduplication [3]. Do not confuse this with the Idempotency-Key header, which is used for API requests you send to Stripe to prevent duplicate actions (e.g., double-charging) [3][4][5]. 2. Atomic Processing: To prevent race conditions, use an atomic operation to record the event ID in your database (e.g., PostgreSQL or Redis) [6][3]. A common pattern is to use an INSERT statement with a unique constraint on the event ID (e.g., ON CONFLICT DO NOTHING) [6][3][5]. This ensures that if two identical requests arrive simultaneously, only one will succeed in marking the event as processed [3][5]. 3. Immediate 2xx Response: Your webhook endpoint should return a 2xx HTTP status code as quickly as possible, ideally before performing complex business logic [1][7]. This signals to Stripe that the event was received, preventing unnecessary retries [3]. 4. Implementation Pattern: - Receive and verify the event signature using Stripe's official library (stripe.webhooks.constructEvent) [1][8]. - Attempt to atomically insert the event.id into your "processed_events" database table [6][9]. - If the insert fails because the ID already exists, the event has already been processed; return a 200 OK immediately and exit [6][3]. - If the insert succeeds, proceed with your business logic (e.g., updating database records) [6]. 5. TTL and Storage: You do not need to store event IDs indefinitely. A duration covering Stripe's retry window (typically up to 3 days) plus a safety margin is generally sufficient [3]. Important Distinctions: - Webhook vs. API Idempotency: Incoming Stripe events require you to perform deduplication [3][5]. Outgoing API requests to Stripe should use the Idempotency-Key header to ensure your actions are not duplicated if your request is retried [4][5]. - Thin vs. Snapshot Events: During migration periods, you may receive both thin and snapshot versions of an event [9]. Use the provided snapshot_event ID field in thin events as your idempotency key to link them and prevent redundant processing [9].
Citations:
- 1: https://docs.stripe.com/webhooks?lang=node
- 2: https://stripe.dev/blog/building-resilient-webhook-handlers-aws-dlqs-stripe-events
- 3: https://www.hooklistener.com/learn/webhook-idempotency-and-deduplication
- 4: https://docs.stripe.com/api/idempotent_requests?api-version=2026-03-25.dahlia
- 5: https://gembait.com/en/blog/stripe-webhook-idempotency-race-condition
- 6: https://userefract.io/blog/article/idempotent-stripe-webhooks-in-nodejs-typescript-patterns-that-survive-replay
- 7: https://docs.stripe.com/webhooks
- 8: https://deepwiki.com/stripe/stripe-node/3.2-webhook-processing
- 9: https://docs.stripe.com/webhooks/migrate-snapshot-to-thin-events
Implement durable webhook deduplication before dispatch.
This route dispatches after signature verification but does not atomically track event.id, so Stripe retries can invoke handlers again. Persist/claim event.id atomically, return 200 for already-processed events, and preserve the claim for the business work path so failures are retriable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rules/stripe-integration-nextjs-typescript.mdc` around lines 67 - 76, Update
the webhook flow before the event.type switch to atomically claim and persist
event.id after signature verification. Return HTTP 200 immediately when the
event was already claimed or processed, and retain the claim through dispatch so
handler failures leave the event eligible for retry rather than marking it
successfully completed.
What this adds
Two new rules covering widely-used API integrations that aren't currently in the list:
1.
rules/stripe-integration-nextjs-typescript.mdc2.
rules/anthropic-claude-api-typescript.mdcREADME updates
Both rules follow the required frontmatter format (
description,globs,alwaysApply: false).Summary by CodeRabbit