@dodopayments/convex コンポーネントは、Dodo Payments を Convex バックエンドに追加します。チェックアウトセッションを作成する checkout 関数、サインイン済みユーザーの Customer Portal を開く customerPortal 関数、および Convex HTTP action で webhook を検証する createDodoWebhookHandler を提供します。Convex 1.26 以降が必要です。
Checkout Function
Convex actions からチェックアウトセッションを作成します。
Customer Portal
顧客がサブスクリプションと詳細情報を管理できるようにします。
Webhooks
Dodo Payments webhook イベントを受信して処理します。
インストール
1
Install the Package
プロジェクトのルートで次のコマンドを実行します。
npm install @dodopayments/convex
2
Add Component to Convex Config
Dodo Payments コンポーネントを Convex 設定に追加します。
// convex/convex.config.ts
import { defineApp } from "convex/server";
import dodopayments from "@dodopayments/convex/convex.config";
const app = defineApp();
app.use(dodopayments);
export default app;
convex.config.ts を編集したら、npx convex dev を一度実行して型を生成します。3
Set Up Environment Variables
Convex dashboard の Settings → Environment Variables で環境変数を設定します。dashboard を開くには、次を実行します。次の環境変数を追加します。
npx convex dashboard
DODO_PAYMENTS_API_KEY: Dodo Payments API key。Dodo Payments dashboard の Developer → API Keys から取得します。DODO_PAYMENTS_ENVIRONMENT:test_modeまたはlive_mode。DODO_PAYMENTS_WEBHOOK_SECRET: webhook secret。Developer → Webhooks の Dodo Payments dashboard から取得します。webhook 処理に必要です。webhook handler はこの正確な変数名を読み取ります。
シークレットは Convex 環境変数として保存します。Convex backend functions は
.env ファイルを読み取りません。シークレットをバージョン管理にコミットしないでください。コンポーネント設定の例
1
Create Internal Query
認証 ID によってデータベース内の顧客を検索する internal query を作成します。次の手順の
identify 関数はこれを使用して、サインイン済みユーザーの Dodo Payments customer ID を取得します。このコンポーネントは schema を定義しません。この query を使用する前に、
convex/schema.ts に by_auth_id index を持つ customers table を定義するか、既存の schema に合わせて query を変更してください。// convex/customers.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";
// Internal query to fetch customer by auth ID
export const getByAuthId = internalQuery({
args: { authId: v.string() },
handler: async (ctx, { authId }) => {
return await ctx.db
.query("customers")
.withIndex("by_auth_id", (q) => q.eq("authId", authId))
.first();
},
});
2
Configure DodoPayments Component
client を作成します。
identify は、サインイン済みの Convex user を Dodo Payments customer ID にマッピングします。サインイン済みの user がいない場合、または一致する customer がない場合は null を返します。// convex/dodo.ts
import { DodoPayments, type DodoPaymentsClientConfig } from "@dodopayments/convex";
import { components } from "./_generated/api";
import { internal } from "./_generated/api";
export const dodo = new DodoPayments(components.dodopayments, {
// This function maps your Convex user to a Dodo Payments customer
// Customize it based on your authentication provider and database
identify: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return null; // User is not logged in
}
// Use ctx.runQuery() to lookup customer from your database
const customer = await ctx.runQuery(internal.customers.getByAuthId, {
authId: identity.subject,
});
if (!customer) {
return null; // Customer not found in database
}
return {
dodoCustomerId: customer.dodoCustomerId, // Replace customer.dodoCustomerId with your field storing Dodo Payments customer ID
};
},
apiKey: process.env.DODO_PAYMENTS_API_KEY!,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
} as DodoPaymentsClientConfig);
// Export the API methods for use in your app
export const { checkout, customerPortal } = dodo.api();
- Checkout Function Setup
- Customer Portal Setup
- Webhook Handler Setup
この function を使用して、Convex app に Dodo Payments checkout を追加します。コンポーネントの checkout payload validator が受け付ける fields から checkout session を作成します。
// convex/payments.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { checkout } from "./dodo";
export const createCheckout = action({
args: {
product_cart: v.array(v.object({
product_id: v.string(),
quantity: v.number(),
})),
returnUrl: v.optional(v.string()),
},
handler: async (ctx, args) => {
try {
const session = await checkout(ctx, {
payload: {
product_cart: args.product_cart,
return_url: args.returnUrl,
billing_currency: "USD",
feature_flags: {
allow_discount_code: true,
},
},
});
if (!session?.checkout_url) {
throw new Error("Checkout session did not return a checkout_url");
}
return session;
} catch (error) {
console.error("Failed to create checkout session", error);
throw new Error("Unable to create checkout session. Please try again.");
}
},
});
この function を使用して、Dodo Payments Customer Portal で顧客がサブスクリプションと詳細情報を管理できるようにします。
identify function が customer を検索します。// convex/payments.ts (add to existing file)
import { action } from "./_generated/server";
import { v } from "convex/values";
import { customerPortal } from "./dodo";
export const getCustomerPortal = action({
args: {
send_email: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
try {
const portal = await customerPortal(ctx, args);
if (!portal?.portal_url) {
throw new Error("Customer portal did not return a portal_url");
}
return portal;
} catch (error) {
console.error("Failed to generate customer portal link", error);
throw new Error("Unable to generate customer portal link. Please try again.");
}
},
});
この handler を使用して Convex app で Dodo Payments webhook events を受信し、signature を検証します。すべての webhook handler は最初の parameter として Convex
ActionCtx を受け取るため、ctx.runQuery() と ctx.runMutation() を呼び出して database を操作できます。// convex/http.ts
import { createDodoWebhookHandler } from "@dodopayments/convex";
import { httpRouter } from "convex/server";
import { internal } from "./_generated/api";
const http = httpRouter();
http.route({
path: "/dodopayments-webhook",
method: "POST",
handler: createDodoWebhookHandler({
// Handle successful payments
onPaymentSucceeded: async (ctx, payload) => {
console.log("🎉 Payment Succeeded!");
// Use Convex context to persist payment data
await ctx.runMutation(internal.webhooks.createPayment, {
paymentId: payload.data.payment_id,
businessId: payload.business_id,
customerEmail: payload.data.customer.email,
amount: payload.data.total_amount,
currency: payload.data.currency,
status: payload.data.status,
webhookPayload: JSON.stringify(payload),
});
},
// Handle subscription activation
onSubscriptionActive: async (ctx, payload) => {
console.log("🎉 Subscription Activated!");
// Use Convex context to persist subscription data
await ctx.runMutation(internal.webhooks.createSubscription, {
subscriptionId: payload.data.subscription_id,
businessId: payload.business_id,
customerEmail: payload.data.customer.email,
status: payload.data.status,
webhookPayload: JSON.stringify(payload),
});
},
// Add other event handlers as needed
}),
});
export default http;
各 handler が呼び出す database mutation を定義します。たとえば、成功した決済を記録する
createPayment mutation や、サブスクリプションの状態を追跡する createSubscription mutation を作成します。この例では、それらが convex/webhooks.ts にあることを想定しています。.convex.site domain から HTTP actions を提供するため、URL は https://<your-deployment-name>.convex.site/dodopayments-webhook です。Checkout Function
Convex コンポーネントは、すべての決済で推奨される checkout flow である checkout session を作成します。session には product cart、customer details、checkout options が含まれます。使用方法
Convex action からcheckout を呼び出し、payload に checkout session fields を指定します。
const result = await checkout(ctx, {
payload: {
product_cart: [{ product_id: "pdt_123", quantity: 1 }],
customer: { email: "user@example.com" },
return_url: "https://example.com/success"
}
});
checkout は identify を呼び出しません。既存の customer を関連付けるには、payload に customer: { customer_id } を渡します。詳細とサポートされている fields の完全な一覧については、Checkout Sessions を参照してください。
payment_method_id で作成された session は checkout URL を返さないため、checkout はその session に対して error を throw します。
Response Format
checkout function は checkout URL を含む object を返します。{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
Customer Portal Function
customer portal function は、サインイン済みユーザー用の Customer Portal URL を返します。使用方法
const result = await customerPortal(ctx, {
send_email: false
});
portal_url field を含む object を返します。
Parameters
boolean
デフォルト:"false"
true に設定すると、Dodo Payments は portal link を customer にメールでも送信します。customerPortal は、DodoPayments setup の identify function から customer を取得します。この function は customer の dodoCustomerId を返す必要があります。identify が null を返す場合、customerPortal は User is not authenticated. error を throw します。Webhook Handler
createDodoWebhookHandler は、コードを実行する前に各 request を検証します。
- Method:
method: "POST"で route を登録します。他の method の request は handler に到達しません。 - Signature Verification:
DODO_PAYMENTS_WEBHOOK_SECRETenvironment variable を使用して Standard Webhooks signature を検証します。検証に失敗すると 400 を返します。 - Payload Validation: Zod で検証します。payload が無効な場合は 400 を返します。
- Error Handling:
- 400: 無効な signature、無効な payload、またはいずれかの handler が throw した error
- 200: すべての handler が完了
DODO_PAYMENTS_WEBHOOK_SECRETが設定されていない場合、handler は error を throw し、request は失敗します。
- Event Routing: すべての event に対して
onPayloadを呼び出し、その後 event の type に対応する handler を呼び出します。
サポートされている Webhook Event Handlers
各 handler は、対応する event type の ConvexActionCtx と検証済み payload を受け取ります。
onPayload?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentProcessing?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPaymentCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onRefundSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onRefundFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeOpened?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeAccepted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeChallenged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeWon?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDisputeLost?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionActive?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionOnHold?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionRenewed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionPlanChanged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionUpdated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionPaused?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionUnpaused?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onSubscriptionUpdatePaymentMethod?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onLicenseKeyCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onAbandonedCheckoutDetected?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onAbandonedCheckoutRecovered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDunningStarted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onDunningRecovered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditAdded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditDeducted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditRolledOver?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditRolloverForfeited?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditOverageCharged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditOverageReset?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditManualAdjustment?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onCreditBalanceLow?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantDelivered?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onEntitlementGrantRevoked?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutOnHold?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutInProgress?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
onPayoutSuccess?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise<void>;
Frontend Usage
convex/react の useAction hook を使用して、React コンポーネントから checkout action と portal action を呼び出します。
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";
export function CheckoutButton() {
const createCheckout = useAction(api.payments.createCheckout);
const handleCheckout = async () => {
try {
const { checkout_url } = await createCheckout({
product_cart: [{ product_id: "pdt_123", quantity: 1 }],
returnUrl: "https://example.com/success"
});
if (!checkout_url) {
throw new Error("Missing checkout_url in response");
}
window.location.href = checkout_url;
} catch (error) {
console.error("Failed to create checkout", error);
throw new Error("Unable to create checkout. Please try again.");
}
};
return <button onClick={handleCheckout}>Buy Now</button>;
}
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";
export function CustomerPortalButton() {
const getPortal = useAction(api.payments.getCustomerPortal);
const handlePortal = async () => {
try {
const { portal_url } = await getPortal({ send_email: false });
if (!portal_url) {
throw new Error("Missing portal_url in response");
}
window.location.href = portal_url;
} catch (error) {
console.error("Unable to open customer portal", error);
alert("We couldn't open the customer portal. Please try again.");
}
};
return <button onClick={handlePortal}>Manage Subscription</button>;
}
LLM 向けプロンプト
この prompt を AI coding assistant にコピーして、プロジェクトにコンポーネントを追加させます。agent に Dodo Payments の docs と skills も提供するには、Agent Plugin をインストールしてください。You are an expert Convex developer assistant. Your task is to guide a user through integrating the @dodopayments/convex component into their existing Convex application.
The @dodopayments/convex adapter provides a Convex component for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, built using the official Convex component architecture pattern.
First, install the necessary package:
npm install @dodopayments/convex
Here's how you should structure your response:
1. Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/convex component would you like to integrate into your project? You can choose one or more of the following:
- Checkout Function (for handling product checkouts)
- Customer Portal Function (for managing customer subscriptions/details)
- Webhook Handler (for receiving Dodo Payments webhook events)
- All (integrate all three)"
2. Based on the user's selection, provide detailed integration steps for each chosen functionality.
If Checkout Function is selected:
Purpose: This function handles session-based checkout flows and returns checkout URLs for programmatic handling.
Integration Steps:
Step 1: Add the component to your Convex configuration.
// convex/convex.config.ts
import { defineApp } from "convex/server";
import dodopayments from "@dodopayments/convex/convex.config";
const app = defineApp();
app.use(dodopayments);
export default app;
Step 2: Guide the user to set up environment variables in the Convex dashboard. Instruct them to open the dashboard by running:
npx convex dashboard
Then add the required environment variables (e.g., DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_ENVIRONMENT, DODO_PAYMENTS_WEBHOOK_SECRET) in **Settings → Environment Variables**. Do not use .env files for backend functions.
Step 3: Create an internal query to fetch customers from your database.
Note: Ensure the user has appropriate schema defined in their convex/schema.ts file or modify the query to match their existing schema.
// convex/customers.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";
// Internal query to fetch customer by auth ID
export const getByAuthId = internalQuery({
args: { authId: v.string() },
handler: async (ctx, { authId }) => {
return await ctx.db
.query("customers")
.withIndex("by_auth_id", (q) => q.eq("authId", authId))
.first();
},
});
Step 4: Create your payment functions file.
// convex/dodo.ts
import { DodoPayments, type DodoPaymentsClientConfig } from "@dodopayments/convex";
import { components } from "./_generated/api";
import { internal } from "./_generated/api";
export const dodo = new DodoPayments(components.dodopayments, {
// This function maps your Convex user to a Dodo Payments customer
// Customize it based on your authentication provider and user database
identify: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return null; // User is not logged in
}
// Use ctx.runQuery() to lookup customer from your database
const customer = await ctx.runQuery(internal.customers.getByAuthId, {
authId: identity.subject,
});
if (!customer) {
return null; // Customer not found in database
}
return {
dodoCustomerId: customer.dodoCustomerId, // Replace customer.dodoCustomerId with your field storing Dodo Payments customer ID
};
},
apiKey: process.env.DODO_PAYMENTS_API_KEY!,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
} as DodoPaymentsClientConfig);
// Export the API methods for use in your app
export const { checkout, customerPortal } = dodo.api();
Step 5: Create actions that use the checkout function.
// convex/payments.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { checkout } from "./dodo";
// Checkout session with full feature support
export const createCheckout = action({
args: {
product_cart: v.array(v.object({
product_id: v.string(),
quantity: v.number(),
})),
returnUrl: v.optional(v.string()),
},
handler: async (ctx, args) => {
return await checkout(ctx, {
payload: {
product_cart: args.product_cart,
return_url: args.returnUrl,
billing_currency: "USD",
feature_flags: {
allow_discount_code: true,
},
},
});
},
});
Step 6: Use in your frontend.
// Your frontend component
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";
export function CheckoutButton() {
const createCheckout = useAction(api.payments.createCheckout);
const handleCheckout = async () => {
const { checkout_url } = await createCheckout({
product_cart: [{ product_id: "pdt_123", quantity: 1 }],
});
window.location.href = checkout_url;
};
return <button onClick={handleCheckout}>Buy Now</button>;
}
Configuration Details:
- `checkout()`: Checkout session with full feature support using session checkout.
- Returns: `{"checkout_url": "https://checkout.dodopayments.com/..."}`
For complete API documentation, refer to:
- Checkout Sessions: https://docs.dodopayments.com/developer-resources/checkout-session
- One-time Payments: https://docs.dodopayments.com/api-reference/payments/post-payments
- Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions
If Customer Portal Function is selected:
Purpose: This function allows customers to manage their subscriptions and payment methods. The customer is automatically identified via the `identify` function.
Integration Steps:
Follow Steps 1-4 from the Checkout Function section, then:
Step 5: Create a customer portal action.
// convex/payments.ts (add to existing file)
import { action } from "./_generated/server";
import { v } from "convex/values";
import { customerPortal } from "./dodo";
export const getCustomerPortal = action({
args: {
send_email: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
try {
const portal = await customerPortal(ctx, args);
if (!portal?.portal_url) {
throw new Error("Customer portal did not return a portal_url");
}
return portal;
} catch (error) {
console.error("Failed to generate customer portal link", error);
throw new Error("Unable to generate customer portal link. Please retry.");
}
},
});
Step 6: Use in your frontend.
// Your frontend component
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";
export function CustomerPortalButton() {
const getPortal = useAction(api.payments.getCustomerPortal);
const handlePortal = async () => {
const { portal_url } = await getPortal({ send_email: false });
window.location.href = portal_url;
};
return <button onClick={handlePortal}>Manage Subscription</button>;
}
Configuration Details:
- Requires authenticated user (via `identify` function).
- Customer identification is handled automatically by the `identify` function.
- `send_email`: Optional boolean to send portal link via email.
If Webhook Handler is selected:
Purpose: This handler processes incoming webhook events from Dodo Payments, allowing your application to react to events like successful payments or subscription changes.
Integration Steps:
Step 1: Add the webhook secret to your environment variables in the Convex dashboard.
Guide the user to open the Convex dashboard by running:
npx convex dashboard
In the dashboard, go to **Settings → Environment Variables** and add:
- `DODO_PAYMENTS_WEBHOOK_SECRET=whsec_...`
Do not use .env files for backend functions; always set secrets in the Convex dashboard. The webhook handler reads this exact variable name.
Step 2: Create a file `convex/http.ts`:
// convex/http.ts
import { createDodoWebhookHandler } from "@dodopayments/convex";
import { httpRouter } from "convex/server";
import { internal } from "./_generated/api";
const http = httpRouter();
http.route({
path: "/dodopayments-webhook",
method: "POST",
handler: createDodoWebhookHandler({
// Handle successful payments
onPaymentSucceeded: async (ctx, payload) => {
console.log("🎉 Payment Succeeded!");
// Use Convex context to persist payment data
await ctx.runMutation(internal.webhooks.createPayment, {
paymentId: payload.data.payment_id,
businessId: payload.business_id,
customerEmail: payload.data.customer.email,
amount: payload.data.total_amount,
currency: payload.data.currency,
status: payload.data.status,
webhookPayload: JSON.stringify(payload),
});
},
// Handle subscription activation
onSubscriptionActive: async (ctx, payload) => {
console.log("🎉 Subscription Activated!");
// Use Convex context to persist subscription data
await ctx.runMutation(internal.webhooks.createSubscription, {
subscriptionId: payload.data.subscription_id,
businessId: payload.business_id,
customerEmail: payload.data.customer.email,
status: payload.data.status,
webhookPayload: JSON.stringify(payload),
});
},
// Add other event handlers as needed
}),
});
export default http;
Note: Make sure to define the corresponding database mutations in your Convex backend for each webhook event you want to handle. For example, create a `createPayment` mutation to record successful payments or a `createSubscription` mutation to manage subscription state.
Now, you can set the webhook endpoint URL in your Dodo Payments dashboard to `https://<your-deployment-name>.convex.site/dodopayments-webhook`. Convex serves HTTP actions from the deployment's .convex.site domain, not .convex.cloud.
Environment Variable Setup:
Set up the following environment variables in your Convex dashboard if you haven't already (Settings → Environment Variables):
- `DODO_PAYMENTS_API_KEY` - Your Dodo Payments API key
- `DODO_PAYMENTS_ENVIRONMENT` - Set to `test_mode` or `live_mode`
- `DODO_PAYMENTS_WEBHOOK_SECRET` - Your webhook secret (required for webhook handling)
Usage in your component configuration:
apiKey: process.env.DODO_PAYMENTS_API_KEY
environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode"
Important: Never commit sensitive environment variables directly into your code. Always use Convex environment variables for all sensitive information.
If the user needs assistance setting up environment variables or deployment, ask them about their specific setup and provide guidance accordingly.
Run `npx convex dev` after setting up the component to generate the necessary types.