Skip to content

feat: add dodo as a payment provider#980

Open
atybdot wants to merge 35 commits intoAmanVarshney01:mainfrom
atybdot:main
Open

feat: add dodo as a payment provider#980
atybdot wants to merge 35 commits intoAmanVarshney01:mainfrom
atybdot:main

Conversation

@atybdot
Copy link

@atybdot atybdot commented Mar 19, 2026

Add Dodo Payments as a first‑class provider across CLI, template generation, web stack builder, and docs, mirroring Polar integration. Includes Dodo-specific dependencies, env vars, Better Auth wiring, protected /payment/success routes, dashboard parity across frontends, MCP recommendation, and updated CLI/web docs.
new payment providers:

  • polar
  • dodo payments

Summary by CodeRabbit

  • New Features

    • Added Dodo Payments as a supported payment provider option alongside Polar, enabling users to select it via the CLI with --payments dodo.
    • Integrated Dodo Payments across all supported frontend frameworks with dashboard subscription management and payment success pages.
    • Expanded MCP server support to include Dodo.
  • Documentation

    • Updated CLI documentation and compatibility guides to reflect Dodo Payments as an available option.
  • Tests

    • Added test coverage for Dodo Payments integration and compatibility validation.

Added Dodo option in CLI payments dodo to the shared payments enum,
surfaced it in the CLI prompt, and enforced compatibility checks with
provider-specific error messaging. Added a Dodo incompatibility
integration test.
…ates/auth/better-auth/server/base/src/index.ts.hbs
…ates/auth/better-auth/web/astro/src/pages/dashboard.astro.hbs
…ates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs
…ates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs
…ates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs
…ates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs
…ates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs
@vercel
Copy link

vercel bot commented Mar 19, 2026

@atybdot is attempting to deploy a commit to the Better T Stack Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Walkthrough

This PR extends the CLI and template generator to support Dodo Payments as a new payment provider option alongside Polar. The changes include CLI prompts, compatibility validation, environment variable configuration, documentation updates, template files for payment success pages, dashboard components for subscription management, and corresponding tests across all frontend frameworks (React, Vue, Solid, Svelte, Astro).

Changes

Cohort / File(s) Summary
CLI Documentation
apps/cli/README.md, apps/web/content/docs/cli/index.mdx, apps/web/content/docs/cli/options.mdx, apps/web/content/docs/cli/compatibility.mdx, apps/web/content/docs/index.mdx
Updated CLI documentation to include "dodo" as a valid payments provider option and added examples showing Dodo usage alongside Polar.
CLI Implementation
apps/cli/src/prompts/payments.ts, apps/cli/src/utils/compatibility-rules.ts, apps/cli/src/helpers/addons/mcp-setup.ts
Added Dodo to payment options prompt, extended compatibility checks to validate Dodo payments requires Better Auth, and added Dodo Payments as an available MCP server.
CLI Tests
apps/cli/test/addon-setup-regressions.test.ts, apps/cli/test/integration.test.ts, apps/cli/test/payments-dodo.test.ts
Added tests verifying Dodo MCP server inclusion, Dodo incompatibility error handling, and full Dodo payments generation across frontend and backend stacks.
Web Documentation
apps/web/content/docs/cli/compatibility.mdx
Updated compatibility documentation to include Dodo alongside Polar with example CLI commands.
Web UI Logic
apps/web/src/lib/constant.ts, apps/web/src/app/(home)/new/_components/utils.ts
Added Dodo payments option to constants and extended compatibility checks in UI utilities to handle both Polar and Dodo with provider-specific messaging.
Type Definitions
packages/types/src/schemas.ts
Extended PaymentsSchema and McpServerSchema enums to include "dodo" as a valid option.
Template Generator - Core
packages/template-generator/src/processors/env-vars.ts, packages/template-generator/src/processors/payments-deps.ts, packages/template-generator/src/utils/add-deps.ts, packages/template-generator/src/templates.generated.ts
Added Dodo environment variables configuration, extended payments dependency resolution to handle Dodo along with Polar, added Dodo package versions, and generated template integration code for Dodo client initialization and webhook handling.
Environment Schema
packages/template-generator/templates/packages/env/src/server.ts.hbs, packages/template-generator/templates/packages/infra/alchemy.run.ts.hbs
Added DODO\_PAYMENTS environment variables (API key, webhook secret, environment mode) to server configuration schemas.
Authentication Templates
packages/template-generator/templates/auth/better-auth/server/base/src/index.ts.hbs, packages/template-generator/templates/auth/better-auth/web/*/src/lib/auth-client.ts.hbs, packages/template-generator/templates/auth/better-auth/web/astro/src/pages/dashboard.astro.hbs, packages/template-generator/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs, packages/template-generator/templates/auth/better-auth/web/react/*/src/...dashboard.tsx.hbs, packages/template-generator/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs, packages/template-generator/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs
Extended Better Auth templates across all frameworks (React, Vue, Solid, Svelte, Astro, Nuxt) to support Dodo Payments plugin initialization and subscription management via dodopayments API calls alongside Polar methods.
Dodo Payments Templates
packages/template-generator/templates/payments/dodo/server/base/src/lib/payments.ts.hbs, packages/template-generator/templates/payments/dodo/web/astro/src/pages/payment/success.astro.hbs, packages/template-generator/templates/payments/dodo/web/nuxt/app/pages/payment/success.vue.hbs, packages/template-generator/templates/payments/dodo/web/react/next/src/app/payment/success/page.tsx.hbs, packages/template-generator/templates/payments/dodo/web/react/react-router/src/routes/payment-success.tsx.hbs, packages/template-generator/templates/payments/dodo/web/react/tanstack-router/src/routes/payment/success.tsx.hbs, packages/template-generator/templates/payments/dodo/web/react/tanstack-start/src/..., packages/template-generator/templates/payments/dodo/web/solid/src/routes/payment/success.tsx.hbs, packages/template-generator/templates/payments/dodo/web/svelte/src/routes/payment/success/+page.svelte.hbs
Added new Dodo Payments templates including server-side client initialization, and payment success pages across all supported frontend frameworks with authentication guards and checkout ID display.

Possibly related issues

  • The changes directly implement Dodo Payments as a first-class payment provider with parity to Polar across the entire CLI, scaffolding, and template system, addressing full integration requirements for Dodo support.

Possibly related PRs

  • PR that modified auth/better-auth plugin configuration for payments integrations, as both PRs extend the plugins array with payment provider clients.
  • PR that added or modified MCP server setup and management, as this PR extends the MCP server list with Dodo Payments support.
  • PR that adjusted compatibility validation rules for payment providers, as both PRs modify the core compatibility check logic to handle multiple payment providers.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add dodo as a payment provider' is concise, clear, and directly summarizes the main change—adding Dodo Payments as a payment option throughout the codebase. It matches the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can scan for known vulnerabilities in your dependencies using OSV Scanner.

OSV Scanner will automatically detect and report security vulnerabilities in your project's dependencies. No additional configuration is required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/template-generator/templates/auth/better-auth/server/base/src/index.ts.hbs (2)

322-326: ⚠️ Potential issue | 🔴 Critical

Missing Dodo imports in mongoose section.

The Dodo plugin block (lines 380–404) uses dodopayments, checkout, portal, webhooks, and dodoPaymentsClient, but there is no corresponding import block for the mongoose ORM path. Generated code will fail when payments === "dodo".

🐛 Proposed fix to add missing Dodo imports
 {{`#if` (eq payments "polar")}}
 import { polar, checkout, portal } from "@polar-sh/better-auth";
 import { polarClient } from "./lib/payments";
 {{/if}}
+{{`#if` (eq payments "dodo")}}
+import { dodopayments, checkout, portal, webhooks } from "@dodopayments/better-auth";
+import { dodoPaymentsClient } from "./lib/payments";
+{{/if}}
 import { client } from "@{{projectName}}/db";

413-417: ⚠️ Potential issue | 🔴 Critical

Missing Dodo imports in "none" ORM section.

Same issue as the mongoose section: lines 471–495 reference Dodo identifiers that are never imported when orm === "none".

🐛 Proposed fix to add missing Dodo imports
 {{`#if` (eq payments "polar")}}
 import { polar, checkout, portal } from "@polar-sh/better-auth";
 import { polarClient } from "./lib/payments";
 {{/if}}
+{{`#if` (eq payments "dodo")}}
+import { dodopayments, checkout, portal, webhooks } from "@dodopayments/better-auth";
+import { dodoPaymentsClient } from "./lib/payments";
+{{/if}}


 export const auth = betterAuth({
🧹 Nitpick comments (5)
packages/template-generator/src/processors/payments-deps.ts (1)

13-24: Consider centralizing the supported web-frontend list.

This inline list is correct, but it’s easy to drift from other modules over time. Consider extracting it to a shared constant/helper used by all payment/template processors.

packages/template-generator/templates/auth/better-auth/server/base/src/index.ts.hbs (1)

84-84: Consider using an environment variable for Dodo's successUrl.

Polar uses env.POLAR_SUCCESS_URL while Dodo has a hardcoded "/payment/success". For deployment flexibility and consistency, consider introducing env.DODO_PAYMENTS_SUCCESS_URL (or reusing a shared env var).

packages/template-generator/templates/payments/dodo/web/astro/src/pages/payment/success.astro.hbs (1)

19-21: Minor: Redundant initial classes on redirect div.

The hidden class will take precedence, hiding the element. While this works (since hidden is removed via JavaScript), having both hidden and flex in the initial class list is slightly confusing. Consider keeping just hidden initially and adding flex via JavaScript when revealing, or use a different approach like inline styles. This is a minor readability nit.

packages/template-generator/templates/payments/dodo/web/react/next/src/app/payment/success/page.tsx.hbs (1)

3-6: Consider conditionally importing authClient.

The authClient import on line 6 is always included but only used when backend !== "self". This results in an unused import in the generated code when the "self" backend is selected.

♻️ Proposed fix to conditionally import authClient
 {{`#if` (eq backend "self")}}
 import { auth } from "@{{projectName}}/auth";
+{{else}}
+import { authClient } from "@/lib/auth-client";
 {{/if}}
-import { authClient } from "@/lib/auth-client";
packages/template-generator/src/templates.generated.ts (1)

6630-6653: Webhook handler only logs events—consider adding a TODO comment.

The onPayload handler is a minimal placeholder that only logs the event type. While this is appropriate for scaffolding, users might not realize they need to implement actual business logic here (e.g., updating subscription status in the database).

Consider adding a TODO comment to guide users:

Suggested improvement
 				webhooks({
 					webhookKey: env.DODO_PAYMENTS_WEBHOOK_SECRET,
 					onPayload: async (payload) => {
+						// TODO: Handle webhook events (e.g., subscription.active, subscription.cancelled)
+						// Update your database accordingly
 						console.log("Received webhook:", payload.event_type);
 					},
 				}),

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 59ab6ec1-aff0-44d5-b49a-ca635156852b

📥 Commits

Reviewing files that changed from the base of the PR and between 7f751a9 and 8351acb.

📒 Files selected for processing (45)
  • apps/cli/README.md
  • apps/cli/src/helpers/addons/mcp-setup.ts
  • apps/cli/src/prompts/payments.ts
  • apps/cli/src/utils/compatibility-rules.ts
  • apps/cli/test/addon-setup-regressions.test.ts
  • apps/cli/test/integration.test.ts
  • apps/cli/test/payments-dodo.test.ts
  • apps/web/content/docs/cli/compatibility.mdx
  • apps/web/content/docs/cli/index.mdx
  • apps/web/content/docs/cli/options.mdx
  • apps/web/content/docs/index.mdx
  • apps/web/src/app/(home)/new/_components/utils.ts
  • apps/web/src/lib/constant.ts
  • packages/template-generator/src/processors/env-vars.ts
  • packages/template-generator/src/processors/payments-deps.ts
  • packages/template-generator/src/templates.generated.ts
  • packages/template-generator/src/utils/add-deps.ts
  • packages/template-generator/templates/auth/better-auth/server/base/src/index.ts.hbs
  • packages/template-generator/templates/auth/better-auth/web/astro/src/lib/auth-client.ts.hbs
  • packages/template-generator/templates/auth/better-auth/web/astro/src/pages/dashboard.astro.hbs
  • packages/template-generator/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs
  • packages/template-generator/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs
  • packages/template-generator/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs
  • packages/template-generator/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs
  • packages/template-generator/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs
  • packages/template-generator/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs
  • packages/template-generator/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs
  • packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs
  • packages/template-generator/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs
  • packages/template-generator/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs
  • packages/template-generator/templates/auth/better-auth/web/svelte/src/lib/auth-client.ts.hbs
  • packages/template-generator/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs
  • packages/template-generator/templates/packages/env/src/server.ts.hbs
  • packages/template-generator/templates/packages/infra/alchemy.run.ts.hbs
  • packages/template-generator/templates/payments/dodo/server/base/src/lib/payments.ts.hbs
  • packages/template-generator/templates/payments/dodo/web/astro/src/pages/payment/success.astro.hbs
  • packages/template-generator/templates/payments/dodo/web/nuxt/app/pages/payment/success.vue.hbs
  • packages/template-generator/templates/payments/dodo/web/react/next/src/app/payment/success/page.tsx.hbs
  • packages/template-generator/templates/payments/dodo/web/react/react-router/src/routes/payment-success.tsx.hbs
  • packages/template-generator/templates/payments/dodo/web/react/tanstack-router/src/routes/payment/success.tsx.hbs
  • packages/template-generator/templates/payments/dodo/web/react/tanstack-start/src/functions/get-payment.ts.hbs
  • packages/template-generator/templates/payments/dodo/web/react/tanstack-start/src/routes/payment/success.tsx.hbs
  • packages/template-generator/templates/payments/dodo/web/solid/src/routes/payment/success.tsx.hbs
  • packages/template-generator/templates/payments/dodo/web/svelte/src/routes/payment/success/+page.svelte.hbs
  • packages/types/src/schemas.ts

Comment on lines +462 to +464
// need to update the dodo logo in r2
icon: `${ICON_BASE_URL}/dodo.svg`,
color: "from-amber-400 to-amber-600",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace the placeholder Dodo icon reference or provide a safe fallback.

The inline note indicates the asset is not ready yet, so this entry can render a broken icon in the UI. Please either ship the icon asset now or use a temporary fallback until it exists.

Comment on lines +29289 to +29304
["payments/dodo/web/react/tanstack-start/src/functions/get-payment.ts.hbs", `import { authClient } from "@/lib/auth-client";
import { authMiddleware } from "@/middleware/auth";
import { createServerFn } from "@tanstack/react-start";
import { getRequestHeaders } from "@tanstack/react-start/server";

export const getPayment = createServerFn({ method: "GET" })
.middleware([authMiddleware])
.handler(async () => {
const { data: customerState } = await authClient.dodopayments.customer.state({
fetchOptions: {
headers: getRequestHeaders(),
},
});
return customerState;
});
`],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify other get-payment templates to understand expected patterns
echo "=== Polar get-payment template for comparison ==="
rg -A 20 "payments/polar.*get-payment" --type ts

echo "=== All Dodo customer API usages ==="
rg "dodopayments\.customer\.(state|subscriptions)" --type ts -B2 -A5

Repository: AmanVarshney01/create-better-t-stack

Length of output: 7467


🏁 Script executed:

#!/bin/bash
# Find all Dodo get-payment implementations across frameworks
echo "=== All Dodo get-payment templates ==="
rg "payments/dodo.*get-payment" -A 15 --type ts

echo -e "\n=== Dashboard components that use customerState ==="
rg "customerState.*data.*items" --type ts -B3 -A3

Repository: AmanVarshney01/create-better-t-stack

Length of output: 9240


🏁 Script executed:

#!/bin/bash
# Find all get-payment templates across all payment providers and frameworks
echo "=== All get-payment templates in codebase ==="
rg "get-payment.*\.hbs" --type ts | head -20

# Verify there's only one Dodo get-payment
echo -e "\n=== Dodo get-payment count ==="
rg "payments/dodo.*get-payment" --type ts | wc -l

Repository: AmanVarshney01/create-better-t-stack

Length of output: 524


API mismatch: get-payment.ts uses incorrect API method and destructuring pattern.

This is the only Dodo get-payment template in the codebase, and it uses authClient.dodopayments.customer.state() with incorrect destructuring. All other Dodo templates and dashboard components consistently use authClient.dodopayments.customer.subscriptions.list() and expect a response with { data: { items: [...] } } structure.

The current code destructures const { data: customerState } = await authClient.dodopayments.customer.state() and returns customerState, which removes the data wrapper. Dashboard components across all frameworks check:

const hasProSubscription = (customerState?.data?.items?.length ?? 0) > 0;

This will fail because customerState?.data will be undefined.

Proposed fix to align with all other Dodo templates
 export const getPayment = createServerFn({ method: "GET" })
 	.middleware([authMiddleware])
 	.handler(async () => {
-		const { data: customerState } = await authClient.dodopayments.customer.state({
+		const customerState = await authClient.dodopayments.customer.subscriptions.list({
+			query: {
+				limit: 10,
+				page: 1,
+				active: true,
+			},
 			fetchOptions: {
 				headers: getRequestHeaders(),
 			},
 		});
 		return customerState;
 	});

Comment on lines +29305 to +29329
["payments/dodo/web/react/tanstack-start/src/routes/payment/success.tsx.hbs", `import { createFileRoute, useSearch } from "@tanstack/react-router";
import { getUser } from "@/functions/get-user";

export const Route = createFileRoute("/payment/success")({
component: SuccessPage,
validateSearch: (search) => ({
checkout_id: search.checkout_id as string,
}),
beforeLoad: async () => {
const session = await getUser();
return { session };
},
});

function SuccessPage() {
const { checkout_id } = useSearch({ from: "/payment/success" });

return (
<div className="container mx-auto px-4 py-8">
<h1>Payment Successful!</h1>
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
</div>
);
}
`],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what getUser returns when no session exists
rg -A 15 "export const getUser" --type ts

Repository: AmanVarshney01/create-better-t-stack

Length of output: 2279


🏁 Script executed:

#!/bin/bash
# Find the TanStack Router payment success page mentioned at lines 29266-29274
sed -n '29266,29274p' packages/template-generator/src/templates.generated.ts

Repository: AmanVarshney01/create-better-t-stack

Length of output: 260


🏁 Script executed:

#!/bin/bash
# Search for other payment success page templates to see their auth patterns
rg "payment/success" packages/template-generator/src/templates.generated.ts | head -20

Repository: AmanVarshney01/create-better-t-stack

Length of output: 1313


🏁 Script executed:

#!/bin/bash
# Look for redirect patterns in middleware and server functions
rg "throw redirect" packages/template-generator/src/templates.generated.ts | head -10

Repository: AmanVarshney01/create-better-t-stack

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Check the full TanStack Start success page template to see if there's any route guard
rg -B 5 -A 30 'payments/dodo/web/react/tanstack-start/src/routes/payment/success.tsx.hbs' packages/template-generator/src/templates.generated.ts | head -50

Repository: AmanVarshney01/create-better-t-stack

Length of output: 1105


🏁 Script executed:

#!/bin/bash
# Check if TanStack Start templates import redirect and how they use it
rg -A 3 'tanstack-start.*redirect' packages/template-generator/src/templates.generated.ts | head -20

Repository: AmanVarshney01/create-better-t-stack

Length of output: 62


Add explicit session validation in beforeLoad.

The getUser() server function with authMiddleware retrieves the session but does not redirect on missing authentication. Unlike the TanStack Router variant which explicitly checks if (!session.data) and redirects, this template lacks that protection. Unauthenticated users could reach the success page.

Add the following check for consistency and safety:

Suggested improvement
 	beforeLoad: async () => {
 		const session = await getUser();
+		if (!session) {
+			throw redirect({
+				to: "/login",
+			});
+		}
 		return { session };
 	},
📝 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
["payments/dodo/web/react/tanstack-start/src/routes/payment/success.tsx.hbs", `import { createFileRoute, useSearch } from "@tanstack/react-router";
import { getUser } from "@/functions/get-user";
export const Route = createFileRoute("/payment/success")({
component: SuccessPage,
validateSearch: (search) => ({
checkout_id: search.checkout_id as string,
}),
beforeLoad: async () => {
const session = await getUser();
return { session };
},
});
function SuccessPage() {
const { checkout_id } = useSearch({ from: "/payment/success" });
return (
<div className="container mx-auto px-4 py-8">
<h1>Payment Successful!</h1>
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
</div>
);
}
`],
import { createFileRoute, useSearch, redirect } from "@tanstack/react-router";
import { getUser } from "@/functions/get-user";
export const Route = createFileRoute("/payment/success")({
component: SuccessPage,
validateSearch: (search) => ({
checkout_id: search.checkout_id as string,
}),
beforeLoad: async () => {
const session = await getUser();
if (!session) {
throw redirect({
to: "/login",
});
}
return { session };
},
});
function SuccessPage() {
const { checkout_id } = useSearch({ from: "/payment/success" });
return (
<div className="container mx-auto px-4 py-8">
<h1>Payment Successful!</h1>
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
</div>
);
}

Comment on lines +21 to +25
{{#if (eq payments "polar")}}
customerState: ReturnType<typeof authClient.customer.state>;
{{/if}}
{{else if (eq payments "dodo")}}
customerState: { data?: { items?: unknown[] } };
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's read the template file to see the full context
cat -n "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs"

Repository: AmanVarshney01/create-better-t-stack

Length of output: 2664


🏁 Script executed:

# Find files that might define authClient or provide type information
find "packages" -name "*.ts" -o -name "*.tsx" -o -name "*.d.ts" | head -20

Repository: AmanVarshney01/create-better-t-stack

Length of output: 939


🏁 Script executed:

# Search for authClient.customer.state definition
rg "authClient\.customer\.state" --type ts --type tsx

Repository: AmanVarshney01/create-better-t-stack

Length of output: 107


🏁 Script executed:

# Search for Better Auth package to understand the API
find . -name "*auth*" -type f \( -name "*.ts" -o -name "*.tsx" \) | grep -E "(client|index)" | head -10

Repository: AmanVarshney01/create-better-t-stack

Length of output: 62


🏁 Script executed:

# Let me search for where this Dashboard component is called
rg "Dashboard" --type ts --type js --type tsx -A 3 -B 3 | grep -E "(customerState|import.*Dashboard|<Dashboard)" | head -30

Repository: AmanVarshney01/create-better-t-stack

Length of output: 107


🏁 Script executed:

# Let's also check if there's a layout or parent component that prepares customerState
find "packages/template-generator/templates" -name "*.hbs" | xargs rg "customerState" -B 5 -A 5 | head -60

Repository: AmanVarshney01/create-better-t-stack

Length of output: 7580


🏁 Script executed:

# Let's find the auth client setup to understand the API
find . -path "*/lib/auth-client*" -o -path "*/auth/client*" -o -path "*better-auth*" | head -20

Repository: AmanVarshney01/create-better-t-stack

Length of output: 1813


🏁 Script executed:

# Search for actual usage of authClient.customer.state in the generated code or examples
rg "customer\.state" --type ts --type js | head -20

Repository: AmanVarshney01/create-better-t-stack

Length of output: 1291


🏁 Script executed:

# Check the Better Auth documentation or types by searching for how customerState is typically prepared
rg "authClient\.customer" --type ts --type js -A 2 | head -40

Repository: AmanVarshney01/create-better-t-stack

Length of output: 2998


🏁 Script executed:

# Let me find where the Dashboard component is instantiated and how customerState is prepared
find "packages/template-generator/templates" -name "*.hbs" -path "*dashboard*" ! -name "*dashboard.tsx.hbs"

Repository: AmanVarshney01/create-better-t-stack

Length of output: 776


🏁 Script executed:

# Check the layout or parent component for the same path
cat -n "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/layout.tsx.hbs"

Repository: AmanVarshney01/create-better-t-stack

Length of output: 204


🏁 Script executed:

# Let me search for the layout file in the same directory
ls -la "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/" 2>/dev/null || find "packages/template-generator/templates/auth/better-auth/web/react/next" -name "*.hbs" -type f

Repository: AmanVarshney01/create-better-t-stack

Length of output: 296


🏁 Script executed:

# Let's look at the page.tsx.hbs which should import the Dashboard component
cat -n "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs"

Repository: AmanVarshney01/create-better-t-stack

Length of output: 1659


🏁 Script executed:

# Let's also check for a layout file at a higher level
find "packages/template-generator/templates/auth/better-auth/web/react/next" -name "layout.tsx.hbs" -o -name "layout.tsx"

Repository: AmanVarshney01/create-better-t-stack

Length of output: 62


🏁 Script executed:

# Let me search for how customerState is passed to the Dashboard in the Next.js variant
rg "Dashboard" "packages/template-generator/templates/auth/better-auth/web/react/next" -A 5 -B 5

Repository: AmanVarshney01/create-better-t-stack

Length of output: 4677


🏁 Script executed:

# Let me check if there's a Better Auth type definition or example showing the actual shape
rg "activeSubscriptions" --type ts --type js

Repository: AmanVarshney01/create-better-t-stack

Length of output: 1869


🏁 Script executed:

# Let's search for any type definitions or documentation about better-auth customer.state response
rg "customer\.state" "packages/template-generator" -B 3 -A 5 | grep -E "(type|interface|activeSubscriptions|data)" | head -40

Repository: AmanVarshney01/create-better-t-stack

Length of output: 4833


🏁 Script executed:

# Let's verify the suggested fix syntax is correct by checking if Awaited is used elsewhere in the templates
rg "Awaited" "packages/template-generator/templates" -B 2 -A 2

Repository: AmanVarshney01/create-better-t-stack

Length of output: 668


Fix the customerState prop type to match the resolved data shape being passed.

In page.tsx.hbs, line 28 destructures { data: customerState } from the Polar response before passing it to Dashboard, but the prop type at line 22 uses ReturnType<typeof authClient.customer.state> — the unresolved async return type, not the unwrapped .data object. This causes a TypeScript mismatch: lines 36–37 access customerState?.activeSubscriptions, which does not exist on the Promise-like return type. The Dodo branch (line 34) has the same structural issue with a handwritten shape that can drift from the actual client response.

Use Awaited<ReturnType<...>>["data"] for Polar and apply the same pattern to Dodo to derive the type consistently from the actual auth client method.

Suggested fix
+{{`#if` (eq payments "polar")}}
+type CustomerState = Awaited<
+  ReturnType<typeof authClient.customer.state>
+>["data"];
+{{else if (eq payments "dodo")}}
+type CustomerState = Awaited<
+  ReturnType<typeof authClient.dodopayments.customer.subscriptions.list>
+>;
+{{/if}}
+
 export default function Dashboard({
 {{`#if` (or (eq payments "polar") (eq payments "dodo"))}}
 	customerState,
 {{/if}}
 	session
 }: {
-{{`#if` (eq payments "polar")}}
-	customerState: ReturnType<typeof authClient.customer.state>;
-{{else if (eq payments "dodo")}}
-	customerState: { data?: { items?: unknown[] } };
-{{/if}}
+{{`#if` (or (eq payments "polar") (eq payments "dodo"))}}
+	customerState: CustomerState;
+{{/if}}
 	session: typeof authClient.$Infer.Session;
 }) {
📝 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
{{#if (eq payments "polar")}}
customerState: ReturnType<typeof authClient.customer.state>;
{{/if}}
{{else if (eq payments "dodo")}}
customerState: { data?: { items?: unknown[] } };
{{/if}}
{{`#if` (eq payments "polar")}}
type CustomerState = Awaited<
ReturnType<typeof authClient.customer.state>
>["data"];
{{else if (eq payments "dodo")}}
type CustomerState = Awaited<
ReturnType<typeof authClient.dodopayments.customer.subscriptions.list>
>;
{{/if}}
export default function Dashboard({
{{`#if` (or (eq payments "polar") (eq payments "dodo"))}}
customerState,
{{/if}}
session
}: {
{{`#if` (or (eq payments "polar") (eq payments "dodo"))}}
customerState: CustomerState;
{{/if}}
session: typeof authClient.$Infer.Session;
}) {

Comment on lines +20 to +22
{{#if (or (eq payments "polar") (eq payments "dodo"))}}
const [customerState, setCustomerState] = useState<any>(null);
{{/if}}
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't render the billing state before the subscription request finishes.

customerState starts as null, so Lines 75-101 treat every logged-in user as free on the first paint. Existing subscribers will briefly see the "Free" plan and the upgrade CTA instead of the manage-subscription action. Add a separate loading flag or move this fetch into a loader, and hide the billing UI until the provider-specific request completes.

Also applies to: 49-64, 75-103

Comment on lines +35 to +47
$effect(() => {
if ($sessionQuery.data) {
authClient.dodopayments.customer.subscriptions
.list({
query: {
limit: 10,
page: 1,
active: true,
},
})
.then((data) => {
customerState = data;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle Dodo subscription fetch failures explicitly.

The Promise chain has no .catch, so failed requests can produce unhandled rejections and leave the UI in an incorrect implicit “Free” state.

Proposed fix
 	$effect(() => {
 		if ($sessionQuery.data) {
 			authClient.dodopayments.customer.subscriptions
 				.list({
 					query: {
 						limit: 10,
 						page: 1,
 						active: true,
 					},
 				})
 				.then((data) => {
 					customerState = data;
-				});
+				})
+				.catch(() => {
+					customerState = { data: { items: [] } };
+				});
 		}
 	});

Comment on lines +91 to +95
{{#if (eq payments "dodo")}}
DODO_PAYMENTS_API_KEY: alchemy.secret.env.DODO_PAYMENTS_API_KEY!,
DODO_PAYMENTS_WEBHOOK_SECRET: alchemy.secret.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
DODO_PAYMENTS_ENVIRONMENT: alchemy.env.DODO_PAYMENTS_ENVIRONMENT!,
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add missing Dodo bindings to the Cloudflare Worker("server") section.

Dodo vars were added for web bindings, but the server worker bindings still only handle Polar. For serverDeploy = cloudflare + payments = dodo, generated server code can miss required env bindings at runtime.

Suggested fix
 {{`#if` (eq payments "polar")}}
     POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,
     POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,
 {{/if}}
+{{`#if` (eq payments "dodo")}}
+    DODO_PAYMENTS_API_KEY: alchemy.secret.env.DODO_PAYMENTS_API_KEY!,
+    DODO_PAYMENTS_WEBHOOK_SECRET: alchemy.secret.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
+    DODO_PAYMENTS_ENVIRONMENT: alchemy.env.DODO_PAYMENTS_ENVIRONMENT!,
+{{/if}}

Also applies to: 151-155, 220-224, 308-312

Comment on lines +11 to +30
useEffect(() => {
let active = true;
async function loadSession() {
const session = await authClient.getSession();
if (active && !session.data) {
navigate("/login", { replace: true });
}
}

loadSession();
return () => {
active = false;
};
}, [navigate]);

return (
<div className="container mx-auto px-4 py-8">
<h1>Payment Successful!</h1>
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Protect the page before rendering success content.

At Line 11, auth is checked only after initial render, so unauthenticated users can briefly see the success UI (and checkout_id) before redirect. This weakens route protection.

Suggested fix
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
@@
 export default function PaymentSuccessPage() {
 	const [searchParams] = useSearchParams();
 	const navigate = useNavigate();
+	const [authorized, setAuthorized] = useState<boolean | null>(null);
 	const checkout_id = searchParams.get("checkout_id");
@@
 	useEffect(() => {
 		let active = true;
 		async function loadSession() {
-			const session = await authClient.getSession();
-			if (active && !session.data) {
-				navigate("/login", { replace: true });
-			}
+			try {
+				const session = await authClient.getSession();
+				if (!active) return;
+				if (!session.data) {
+					navigate("/login", { replace: true });
+					return;
+				}
+				setAuthorized(true);
+			} catch {
+				if (active) navigate("/login", { replace: true });
+			}
 		}
@@
 	}, [navigate]);
+
+	if (authorized === null) return null;

Comment on lines +12 to +15
redirect({
to: "/login",
throw: true,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In the exact @tanstack/react-routerversion used by this repository, what is the recommended redirect pattern insidebeforeLoadfor auth guards:throw redirect(...), return redirect(...), or redirect({ throw: true, ... })?

💡 Result:

The recommended redirect pattern inside beforeLoad for auth guards in @tanstack/react-router (all recent versions up to v1.167.4 as of 2026-03-19) is throw redirect({ to: '/login', ... }). This is consistently shown in official documentation and examples for authenticated routes, where beforeLoad checks authentication status and throws the redirect if the user is not authenticated, preventing child routes from loading. Returning the redirect object or using redirect({ throw: true, ... }) (which auto-throws) are alternatives mentioned in the redirect API docs, but throw redirect(...) without explicit throw: true is the standard, recommended pattern for auth guards specifically. return redirect(...) alone does not interrupt route loading and is not recommended for guards.

Citations:


Use the standard throw redirect(...) pattern in beforeLoad.

Line 12 uses redirect({ throw: true }), which auto-throws but deviates from the documented standard for auth guards. Use throw redirect(...) instead for consistency with @tanstack/react-router best practices.

Suggested fix
 		if (!session.data) {
-			redirect({
-				to: "/login",
-				throw: true,
-			});
+			throw redirect({
+				to: "/login",
+			});
 		}
📝 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
redirect({
to: "/login",
throw: true,
});
throw redirect({
to: "/login",
});

Comment on lines +9 to +12
beforeLoad: async () => {
const session = await getUser();
return { session };
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate get-user implementations:"
fd 'get-user\.(ts|tsx|js|mjs|cjs)$' packages/template-generator/templates -HI

echo
echo "Inspect getUser definitions and nearby logic:"
rg -nP -C4 --type=ts --type=tsx 'export\s+(async\s+)?function\s+getUser|const\s+getUser\s*=' packages/template-generator/templates

echo
echo "Inspect auth guarding in this route:"
rg -nP -C4 --type=ts --type=tsx 'createFileRoute\("/payment/success"\)|beforeLoad|redirect\(' packages/template-generator/templates/payments/dodo/web/react/tanstack-start

Repository: AmanVarshney01/create-better-t-stack

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading the target file ==="
cat -n packages/template-generator/templates/payments/dodo/web/react/tanstack-start/src/routes/payment/success.tsx.hbs 2>/dev/null || echo "File not found or unreadable"

echo
echo "=== Search for getUser implementations (corrected) ==="
find packages/template-generator/templates -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.hbs" \) | xargs grep -l "getUser" 2>/dev/null | head -20

echo
echo "=== Inspect getUser definitions ==="
find packages/template-generator/templates -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.hbs" \) | xargs grep -B2 -A8 "function\s*getUser\|const\s*getUser\|export.*getUser" 2>/dev/null | head -80

Repository: AmanVarshney01/create-better-t-stack

Length of output: 2177


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find authMiddleware definition ==="
find packages/template-generator/templates -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.hbs" \) | xargs grep -l "authMiddleware" | head -10

echo
echo "=== Inspect authMiddleware implementation ==="
find packages/template-generator/templates -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.hbs" \) | xargs grep -B3 -A15 "export.*authMiddleware\|const\s*authMiddleware" 2>/dev/null | head -100

echo
echo "=== Check dashboard route for comparison (likely protected) ==="
cat packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs 2>/dev/null | head -50

echo
echo "=== Check if other payment routes have auth guards ==="
find packages/template-generator/templates/payments -type f -name "*.hbs" | xargs grep -l "beforeLoad\|redirect" | head -5

Repository: AmanVarshney01/create-better-t-stack

Length of output: 7623


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full authMiddleware definition ==="
cat packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/middleware/auth.ts.hbs

Repository: AmanVarshney01/create-better-t-stack

Length of output: 875


Add explicit auth check in beforeLoad to prevent unauthenticated access to payment success page.

The beforeLoad fetches session via getUser() but does not check if it's valid before allowing access. While the dashboard route includes an explicit if (!context.session) throw redirect({ to: "/login" }) guard in its loader, this success route has no such protection. Unauthenticated users can reach /payment/success.

Suggested fix
-import { createFileRoute, useSearch } from "@tanstack/react-router";
+import { createFileRoute, redirect, useSearch } from "@tanstack/react-router";
@@
 	beforeLoad: async () => {
 		const session = await getUser();
+		if (!session) {
+			throw redirect({ to: "/login" });
+		}
 		return { session };
 	},
📝 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
beforeLoad: async () => {
const session = await getUser();
return { session };
},
beforeLoad: async () => {
const session = await getUser();
if (!session) {
throw redirect({ to: "/login" });
}
return { session };
},

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant