Tổng quan
Bộ chuyển đổi Better Auth cho Dodo Payments cung cấp:- Tạo khách hàng tự động khi đăng ký
- Phiên thanh toán (được ưu tiên) với ánh xạ slug sản phẩm
- Cổng thông tin khách hàng tự phục vụ
- Điểm thu thập và báo cáo sử dụng theo mức sử dụng cho thanh toán dựa trên mức sử dụng
- Xử lý sự kiện webhook theo thời gian thực với xác minh chữ ký
- Hỗ trợ đầy đủ TypeScript
Bạn cần có tài khoản Dodo Payments và khóa API để sử dụng tích hợp này.
Điều kiện tiên quyết
- Node.js 20+
- Truy cập vào Bảng điều khiển Dodo Payments của bạn
- Dự án hiện có sử dụng better-auth
Cài đặt
Cấu hình
Configure environment variables
Thêm những mục này vào tệp
.env của bạn:Sao chép
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your_better_auth_secret_here
Không bao giờ đưa khóa API hoặc bí mật vào hệ thống kiểm soát phiên bản.
Set up server-side integration
Tạo hoặc cập nhật
src/lib/auth.ts:Sao chép
import { betterAuth } from "better-auth";
import {
dodopayments,
checkout,
portal,
webhooks,
usage,
} from "@dodopayments/better-auth";
import DodoPayments from "dodopayments";
export const dodoPayments = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
environment: "test_mode", // or "live_mode" for production
});
export const { auth, endpoints, client } = BetterAuth({
plugins: [
dodopayments({
client: dodoPayments,
createCustomerOnSignUp: true,
use: [
checkout({
products: [
{
productId: "pdt_xxxxxxxxxxxxxxxxxxxxx",
slug: "premium-plan",
},
],
successUrl: "/dashboard/success",
authenticatedUsersOnly: true,
}),
portal(),
webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
onPayload: async (payload) => {
console.log("Received webhook:", payload.event_type);
},
}),
usage(),
],
}),
],
});
Đặt
environment thành live_mode cho môi trường sản xuất.Set up client-side integration
Tạo hoặc cập nhật
src/lib/auth-client.ts:Sao chép
import { createAuthClient } from "better-auth/react";
import { dodopaymentsClient } from "@dodopayments/better-auth";
export const authClient = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
plugins: [dodopaymentsClient()],
});
Ví dụ sử dụng
Ưu tiên
authClient.dodopayments.checkoutSession cho các tích hợp mới. Phương thức cũ checkout đã bị ngưng và chỉ được giữ lại để tương thích ngược.Tạo một Phiên thanh toán (Được ưu tiên)
Sao chép
const { data: session, error } = await authClient.dodopayments.checkoutSession({
// Option A: use a configured slug
slug: "premium-plan",
// Your internal id for reference
referenceId: "order_123",
});
if (session) {
window.location.href = session.url;
}
Khác với phương thức
checkout cũ, checkoutSession không yêu cầu thông tin thanh toán trước vì nó sẽ được lấy từ người dùng trên trang thanh toán. Bạn vẫn có thể ghi đè bằng cách truyền khóa billing trong đối số.Tương tự phương thức
checkout cũ, checkoutSession lấy email và tên khách hàng từ phiên better-auth của họ, tuy nhiên, bạn có thể ghi đè bằng cách truyền đối tượng customer có email và tên.Bạn có thể truyền cùng các tùy chọn trong đối số như trong thân yêu cầu cho điểm cuối Tạo phiên thanh toán.
URL chuyển hướng về được lấy từ
successUrl đã cấu hình trong plugin máy chủ. Bạn không cần bao gồm return_url trong dữ liệu gửi từ phía khách hàng.Thanh toán cũ (Đã ngừng sử dụng)
Phương thức
authClient.dodopayments.checkout đã bị ngưng. Hãy sử dụng checkoutSession cho các triển khai mới.Sao chép
const { data: checkout, error } = await authClient.dodopayments.checkout({
slug: "premium-plan",
customer: {
email: "customer@example.com",
name: "John Doe",
},
billing: {
city: "San Francisco",
country: "US",
state: "CA",
street: "123 Market St",
zipcode: "94103",
},
referenceId: "order_123",
});
if (checkout) {
window.location.href = checkout.url;
}
Truy cập Cổng thông tin Khách hàng
Sao chép
const { data: customerPortal, error } =
await authClient.dodopayments.customer.portal();
if (customerPortal && customerPortal.redirect) {
window.location.href = customerPortal.url;
}
Liệt kê Dữ liệu Khách hàng
Sao chép
// Get subscriptions
const { data: subscriptions, error } =
await authClient.dodopayments.customer.subscriptions.list({
query: {
limit: 10,
page: 1,
status: "active",
},
});
// Get payment history
const { data: payments, error } =
await authClient.dodopayments.customer.payments.list({
query: {
limit: 10,
page: 1,
status: "succeeded",
},
});
Theo dõi Sử dụng theo mức sử dụng
Kích hoạt pluginusage() trên máy chủ để ghi lại các sự kiện theo mức sử dụng và cho phép khách hàng kiểm tra thanh toán dựa trên mức sử dụng của họ.
authClient.dodopayments.usage.ingestghi lại các sự kiện cho người dùng đã đăng nhập, đã xác minh email.authClient.dodopayments.usage.meters.listliệt kê mức sử dụng gần đây cho các đồng hồ liên kết với các đăng ký của khách hàng đó.
Sao chép
// Record a metered event (e.g., an API request)
const { error: ingestError } = await authClient.dodopayments.usage.ingest({
event_id: crypto.randomUUID(),
event_name: "api_request",
metadata: {
route: "/reports",
method: "GET",
},
// Optional Date; defaults to now if omitted
timestamp: new Date(),
});
if (ingestError) {
console.error("Failed to record usage", ingestError);
}
// List recent usage for the current customer
const { data: usage, error: usageError } =
await authClient.dodopayments.usage.meters.list({
query: {
page_size: 20,
meter_id: "mtr_yourMeterId", // optional
},
});
if (usage?.items) {
usage.items.forEach((event) => {
console.log(event.event_name, event.timestamp, event.metadata);
});
}
Các dấu thời gian cũ hơn một giờ hoặc hơn năm phút so với thời gian hiện tại sẽ bị từ chối khi nhập dữ liệu sử dụng.
Nếu bạn bỏ
meter_id khi liệt kê các công tơ sử dụng, tất cả công tơ liên kết với đăng ký của khách hàng sẽ được trả về.Webhooks
Plugin webhook xử lý các sự kiện thanh toán thời gian thực từ Dodo Payments với xác minh chữ ký bảo mật. Điểm cuối mặc định là
/api/auth/dodopayments/webhooks.Generate and set webhook secret
Tạo một bí mật webhook cho URL điểm cuối của bạn (ví dụ,
https://<your-domain>/api/auth/dodopayments/webhooks) trong Bảng điều khiển Dodo Payments và đặt nó trong tệp .env của bạn:Sao chép
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
Các Trình xử lý Sự kiện Webhook Hỗ trợ
Sao chép
onPayload?: (payload: WebhookPayload) => Promise<void>;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise<void>;
onPaymentFailed?: (payload: WebhookPayload) => Promise<void>;
onPaymentProcessing?: (payload: WebhookPayload) => Promise<void>;
onPaymentCancelled?: (payload: WebhookPayload) => Promise<void>;
onRefundSucceeded?: (payload: WebhookPayload) => Promise<void>;
onRefundFailed?: (payload: WebhookPayload) => Promise<void>;
onDisputeOpened?: (payload: WebhookPayload) => Promise<void>;
onDisputeExpired?: (payload: WebhookPayload) => Promise<void>;
onDisputeAccepted?: (payload: WebhookPayload) => Promise<void>;
onDisputeCancelled?: (payload: WebhookPayload) => Promise<void>;
onDisputeChallenged?: (payload: WebhookPayload) => Promise<void>;
onDisputeWon?: (payload: WebhookPayload) => Promise<void>;
onDisputeLost?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionActive?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise<void>;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise<void>;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise<void>;
Tham khảo Cấu hình
Plugin Options
Plugin Options
- client (bắt buộc): Thể hiện client DodoPayments - createCustomerOnSignUp (tùy chọn): Tự động tạo khách hàng khi người dùng đăng ký - use (bắt buộc): Mảng các plugin cần kích hoạt (checkout, portal, usage, webhooks)
Checkout Plugin Options
Checkout Plugin Options
- products: Mảng sản phẩm hoặc hàm bất đồng bộ trả về sản phẩm - successUrl: URL để chuyển hướng sau khi thanh toán thành công - authenticatedUsersOnly: Yêu cầu người dùng đã xác thực (mặc định: false)
Khắc phục sự cố & Mẹo
Common Issues
Common Issues
- Invalid API key: Kiểm tra kỹ
DODO_PAYMENTS_API_KEYtrong.env. - Webhook signature mismatch: Đảm bảo bí mật webhook khớp với cái đã đặt trong Bảng điều khiển Dodo Payments. - Customer not created: Xác nhậncreateCustomerOnSignUpđược đặt thànhtrue.
Best Practices
Best Practices
- Sử dụng biến môi trường cho tất cả bí mật và khóa. - Kiểm tra trong
test_modetrước khi chuyển sanglive_mode. - Ghi lại sự kiện webhook để gỡ lỗi và kiểm tra.
Lời nhắc cho LLMs
Sao chép
You are a skilled developer helping to integrate the @dodopayments/better-auth adapter into a typescript web application with better-auth. This adapter enables seamless payment processing through Dodo Payments with automatic customer management, checkout flows, and webhook handling.
STAGE 1: BASIC SETUP
This stage covers the foundational setup needed before implementing any plugins. Complete this stage first.
STEP 1: Installation
Install the required dependencies:
npm install @dodopayments/better-auth dodopayments better-auth zod
STEP 2: Environment Variables Setup
You will need to complete these external setup tasks. I will provide you with a TODO list for the actions you need to take outside of the code:
TODO LIST FOR USER:
1. Generate Dodo Payments API Key:
- Go to your Dodo Payments Dashboard > Developer > API Keys
- Create a new API key (or use existing)
- Copy the API key value
- Set environment variable: DODO_PAYMENTS_API_KEY=your_api_key_here
2. Generate Better Auth Secret:
- Generate a random secret key (32+ characters)
- Set environment variable: BETTER_AUTH_SECRET=your_better_auth_secret_here
3. Set Application URL:
- For development: BETTER_AUTH_URL=http://localhost:3000
- For production: BETTER_AUTH_URL=https://your-domain.com
4. Webhook Secret (only if implementing webhooks plugin):
- This will be provided after you specify your domain name in Stage 2
- Set environment variable: DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
Add these environment variables to your .env file:
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your_better_auth_secret_here
STEP 3: Server Configuration
Create or update your better-auth setup file (src/lib/auth.ts):
import { BetterAuth } from "better-auth";
import { dodopayments } from "@dodopayments/better-auth";
import DodoPayments from "dodopayments";
// Create DodoPayments client
export const dodoPayments = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
environment: "test_mode", // Change to "live_mode" for production
});
// Configure better-auth with dodopayments adapter
export const { auth, endpoints, client } = BetterAuth({
plugins: [
dodopayments({
client: dodoPayments,
createCustomerOnSignUp: true, // Auto-create customers on signup
use: [], // We'll add plugins here in Stage 2
}),
],
});
STEP 4: Client Configuration
Create or update your auth client file (src/lib/auth-client.ts):
import { createAuthClient } from "better-auth/react";
import { dodopaymentsClient } from "@dodopayments/better-auth";
export const authClient = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
plugins: [dodopaymentsClient()],
});
STAGE 2: PLUGIN IMPLEMENTATION
After completing Stage 1, you can selectively implement any of these plugins based on your needs. Each plugin is independent and can be added or removed as needed.
PLUGIN SELECTION:
Before implementing any plugins, ask the user which plugins they want to implement:
"Which plugins would you like to implement? You can choose any combination of:
1. CHECKOUT - Enables secure payment processing and checkout flows
2. PORTAL - Provides customer self-service portal for subscriptions and payments
3. USAGE - Tracks metered events and exposes usage history for billing beyond included allowances
4. WEBHOOKS - Handles real-time payment events from Dodo Payments
Please specify which plugins you want (e.g., 'checkout and usage', 'all four', 'just portal', etc.)"
If the user doesn't respond or you cannot prompt the user, implement all four plugins by default.
Based on the user's selection, implement only the requested plugins from the sections below:
CHECKOUT PLUGIN
Purpose: Enables secure payment processing using Checkout Sessions (preferred) with product slug mapping. A legacy checkout endpoint is also exposed for backward compatibility but is deprecated.
SETUP TODO LIST FOR USER:
1. Create products in Dodo Payments Dashboard:
- Go to Dodo Payments Dashboard > Products
- Create your products (e.g., subscription plans, one-time purchases)
- Copy each product ID (starts with "pdt_")
- Note down the product names for creating friendly slugs
2. Plan your checkout URLs:
- Decide on your success URL (e.g., "/dashboard/success", "/thank-you")
- Ensure this URL exists in your application
Configuration:
Add checkout to your imports in src/lib/auth.ts:
import { dodopayments, checkout } from "@dodopayments/better-auth";
Add checkout plugin to the use array in your dodopayments configuration:
use: [
checkout({
products: [
{
productId: "pdt_xxxxxxxxxxxxxxxxxxxxx", // Your actual product ID from Dodo Payments
slug: "premium-plan", // Friendly slug for checkout
},
// Add more products as needed
],
successUrl: "/dashboard/success", // Your success page URL
authenticatedUsersOnly: true, // Require login for checkout
}),
],
Usage Example (Preferred - Checkout Sessions):
const { data: session, error } = await authClient.dodopayments.checkoutSession({
// Use the slug from your configuration OR provide product_cart directly
slug: "premium-plan",
// product_cart: [{ product_id: "pdt_xxxxxxxxxxxxxxxxxxxxx", quantity: 1 }],
referenceId: "order_123", // Optional reference
});
if (session) {
window.location.href = session.url;
}
Deprecated (Legacy Checkout):
const { data: checkout, error } = await authClient.dodopayments.checkout({
slug: "premium-plan",
customer: {
email: "customer@example.com",
name: "John Doe",
},
billing: {
city: "San Francisco",
country: "US",
state: "CA",
street: "123 Market St",
zipcode: "94103",
},
});
if (checkout) {
window.location.href = checkout.url;
}
Options:
- products: Array of products or async function returning products
- successUrl: URL to redirect after successful payment
- authenticatedUsersOnly: Require user authentication (default: false)
PORTAL PLUGIN
Purpose: Provides customer self-service capabilities for managing subscriptions and viewing payment history.
Configuration:
Add portal to your imports in src/lib/auth.ts:
import { dodopayments, portal } from "@dodopayments/better-auth";
Add portal plugin to the use array in your dodopayments configuration:
use: [
portal(),
],
Usage Examples:
// Access customer portal
const { data: customerPortal, error } = await authClient.dodopayments.customer.portal();
if (customerPortal && customerPortal.redirect) {
window.location.href = customerPortal.url;
}
// List customer subscriptions
const { data: subscriptions, error } = await authClient.dodopayments.customer.subscriptions.list({
query: {
limit: 10,
page: 1,
active: true,
},
});
// List customer payments
const { data: payments, error } = await authClient.dodopayments.customer.payments.list({
query: {
limit: 10,
page: 1,
status: "succeeded",
},
});
Note: All portal methods require user authentication.
USAGE PLUGIN (METERED BILLING)
Purpose: Records metered events (like API requests) for usage-based plans and surfaces recent usage history per customer.
SETUP TODO LIST FOR USER:
1. Create or identify usage meters in the Dodo Payments Dashboard:
- Dashboard > Usage > Meters
- Copy the meter IDs (prefixed with <code>mtr_</code>) used by your usage-based plans
2. Decide which event names and metadata you will capture (e.g., <code>api_request</code>, route, method)
3. Ensure BetterAuth users verify their email addresses (the plugin enforces this before ingesting usage)
Configuration:
Add usage to your imports in <code>src/lib/auth.ts</code>:
import { dodopayments, usage } from "@dodopayments/better-auth";
Add the plugin to the <code>use</code> array:
use: [
usage(),
],
Usage Examples:
// Record a metered event for the signed-in customer
const { error: ingestError } = await authClient.dodopayments.usage.ingest({
event_id: crypto.randomUUID(),
event_name: "api_request",
metadata: {
route: "/reports",
method: "GET",
},
// Optional Date or ISO string; defaults to now
timestamp: new Date(),
});
if (ingestError) {
console.error("Failed to record usage", ingestError);
}
// List recent usage for the current customer
const { data: usage, error: usageError } =
await authClient.dodopayments.usage.meters.list({
query: {
page_size: 20,
meter_id: "mtr_yourMeterId", // optional filter
},
});
if (usage?.items) {
usage.items.forEach((event) => {
console.log(event.event_name, event.timestamp, event.metadata);
});
}
<Tip>
The plugin exposes <code>authClient.dodopayments.usage.ingest</code> and
<code>authClient.dodopayments.usage.meters.list</code>. Timestamps older
than one hour or more than five minutes in the future are rejected.
</Tip>
<Tip>
If you do
not pass <code>meter_id</code> when listing usage meters, all meters tied to
the customer’s active subscriptions are returned.
</Tip>
WEBHOOKS PLUGIN
Purpose: Handles real-time payment events from Dodo Payments with secure signature verification.
BEFORE CONFIGURATION - Setup Webhook URL:
First, I need your domain name to generate the webhook URL and provide you with setup instructions.
STEP 1: Domain Name Input
What is your domain name? Please provide:
- For production: your domain name (e.g., "myapp.com", "api.mycompany.com")
- For staging: your staging domain (e.g., "staging.myapp.com")
- For development: use "localhost:3000" (or your local port)
STEP 2: After receiving your domain name, I will:
- Generate your webhook URL: https://[YOUR-DOMAIN]/api/auth/dodopayments/webhooks
- Provide you with a TODO list for webhook setup in Dodo Payments dashboard
- Give you the exact environment variable setup instructions
WEBHOOK SETUP TODO LIST (will be provided after domain input):
1. Configure webhook in Dodo Payments Dashboard:
- Go to Dodo Payments Dashboard > Developer > Webhooks
- Click "Add Webhook" or "Create Webhook"
- Enter webhook URL: https://[YOUR-DOMAIN]/api/auth/dodopayments/webhooks
- Select events you want to receive (or select all)
- Copy the generated webhook secret
2. Set webhook secret in your environment:
- For production: Set DODO_PAYMENTS_WEBHOOK_SECRET in your hosting provider environment
- For staging: Set DODO_PAYMENTS_WEBHOOK_SECRET in your staging environment
- For development: Add DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here to your .env file
3. Deploy your application with the webhook secret configured
STEP 3: Add webhooks to your imports in src/lib/auth.ts:
import { dodopayments, webhooks } from "@dodopayments/better-auth";
STEP 4: Add webhooks plugin to the use array in your dodopayments configuration:
use: [
webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
// Generic handler for all webhook events
onPayload: async (payload) => {
console.log("Received webhook:", payload.event_type);
},
// Payment event handlers
onPaymentSucceeded: async (payload) => {
console.log("Payment succeeded:", payload);
},
onPaymentFailed: async (payload) => {
console.log("Payment failed:", payload);
},
onPaymentProcessing: async (payload) => {
console.log("Payment processing:", payload);
},
onPaymentCancelled: async (payload) => {
console.log("Payment cancelled:", payload);
},
// Refund event handlers
onRefundSucceeded: async (payload) => {
console.log("Refund succeeded:", payload);
},
onRefundFailed: async (payload) => {
console.log("Refund failed:", payload);
},
// Dispute event handlers
onDisputeOpened: async (payload) => {
console.log("Dispute opened:", payload);
},
onDisputeExpired: async (payload) => {
console.log("Dispute expired:", payload);
},
onDisputeAccepted: async (payload) => {
console.log("Dispute accepted:", payload);
},
onDisputeCancelled: async (payload) => {
console.log("Dispute cancelled:", payload);
},
onDisputeChallenged: async (payload) => {
console.log("Dispute challenged:", payload);
},
onDisputeWon: async (payload) => {
console.log("Dispute won:", payload);
},
onDisputeLost: async (payload) => {
console.log("Dispute lost:", payload);
},
// Subscription event handlers
onSubscriptionActive: async (payload) => {
console.log("Subscription active:", payload);
},
onSubscriptionOnHold: async (payload) => {
console.log("Subscription on hold:", payload);
},
onSubscriptionRenewed: async (payload) => {
console.log("Subscription renewed:", payload);
},
onSubscriptionPlanChanged: async (payload) => {
console.log("Subscription plan changed:", payload);
},
onSubscriptionCancelled: async (payload) => {
console.log("Subscription cancelled:", payload);
},
onSubscriptionFailed: async (payload) => {
console.log("Subscription failed:", payload);
},
onSubscriptionExpired: async (payload) => {
console.log("Subscription expired:", payload);
},
// License key event handlers
onLicenseKeyCreated: async (payload) => {
console.log("License key created:", payload);
},
}),
],
Supported Webhook Event Handlers:
- onPayload: Generic handler for all webhook events
- onPaymentSucceeded: Payment completed successfully
- onPaymentFailed: Payment failed
- onPaymentProcessing: Payment is being processed
- onPaymentCancelled: Payment was cancelled
- onRefundSucceeded: Refund completed successfully
- onRefundFailed: Refund failed
- onDisputeOpened: Dispute was opened
- onDisputeExpired: Dispute expired
- onDisputeAccepted: Dispute was accepted
- onDisputeCancelled: Dispute was cancelled
- onDisputeChallenged: Dispute was challenged
- onDisputeWon: Dispute was won
- onDisputeLost: Dispute was lost
- onSubscriptionActive: Subscription became active
- onSubscriptionOnHold: Subscription was put on hold
- onSubscriptionRenewed: Subscription was renewed
- onSubscriptionPlanChanged: Subscription plan was changed
- onSubscriptionCancelled: Subscription was cancelled
- onSubscriptionFailed: Subscription failed
- onSubscriptionExpired: Subscription expired
- onLicenseKeyCreated: License key was created
COMBINING SELECTED PLUGINS:
After implementing the user's selected plugins, update your <code>src/lib/auth.ts</code> file to include all chosen plugins in the imports and <code>use</code> array:
Example for all four plugins:
import {
dodopayments,
checkout,
portal,
usage,
webhooks,
} from "@dodopayments/better-auth";
use: [
checkout({
// checkout configuration
}),
portal(),
usage(),
webhooks({
// webhook configuration
}),
];
Example for checkout + portal + usage:
import { dodopayments, checkout, portal, usage } from "@dodopayments/better-auth";
use: [
checkout({
// checkout configuration
}),
portal(),
usage(),
];
Example for just webhooks:
import { dodopayments, webhooks } from "@dodopayments/better-auth";
use: [
webhooks({
// webhook configuration
}),
];
IMPORTANT NOTES:
1. Complete Stage 1 before implementing any plugins
2. Ask the user which plugins they want to implement, or implement all four if no response
3. Only implement the plugins the user specifically requested
4. ALWAYS provide TODO lists for external actions the user needs to complete:
- API key generation and environment variable setup
- Product creation in Dodo Payments dashboard (for checkout plugin)
- Usage meter configuration and API event definition (for usage plugin)
- Webhook setup in Dodo Payments dashboard (for webhooks plugin)
- Domain name collection for webhook URL generation
5. For webhook plugin: Ask for the user's domain name and generate the exact webhook URL: https://[domain]/api/auth/dodopayments/webhooks
6. The usage plugin requires the BetterAuth session user to exist and have a verified email before ingesting or listing usage
7. All client methods return <code>{ data, error }</code> objects for proper error handling
8. Use <code>test_mode</code> for development and <code>live_mode</code> for production
9. The webhook endpoint is automatically created and secured with signature verification (if webhooks plugin is selected)
10. Customer portal and subscription listing require user authentication (if portal plugin is selected)
11. Handle errors appropriately and test webhook functionality in development before going live
12. Present all external setup tasks as clear TODO lists with specific environment variable names