@dodopayments/convex component는 Convex 백엔드에 Dodo Payments를 추가합니다. checkout session을 생성하는 checkout function, 로그인한 사용자의 Customer Portal을 여는 customerPortal function, 그리고 Convex HTTP action에서 webhook을 검증하는 createDodoWebhookHandler를 제공합니다. Convex 1.26 이상이 필요합니다.
Checkout Function
Convex action에서 checkout session을 생성합니다.
Customer Portal
고객이 subscription과 세부 정보를 관리할 수 있습니다.
Webhooks
Dodo Payments webhook event를 수신하고 처리합니다.
설치
1
Install the Package
프로젝트 루트에서 다음 명령을 실행하세요:
npm install @dodopayments/convex
2
Add Component to Convex Config
Convex configuration에 Dodo Payments component를 추가하세요:
// 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를 한 번 실행하여 type을 생성하세요.3
Set Up Environment Variables
Convex dashboard의 Settings → Environment Variables에서 environment variable을 설정하세요. dashboard를 열려면 다음을 실행하세요:다음 environment variable을 추가하세요:
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는 이 정확한 variable name을 읽습니다.
Secret은 Convex environment variable로 저장하세요. Convex backend function은
.env file을 읽지 않습니다. Secret을 version control에 절대 commit하지 마세요.Component 설정 예시
1
Create Internal Query
auth ID로 database에서 customer를 찾는 internal query를 생성하세요. 다음 단계의
identify function은 이 query를 사용하여 로그인한 사용자의 Dodo Payments customer ID를 customer portal에서 가져옵니다.component는 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을 추가하세요. component의 checkout payload validator가 허용하는 field로 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에서 subscription과 세부 정보를 관리할 수 있도록 하세요.
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 event를 수신하고 signature를 검증하세요. 모든 webhook handler는 Convex
ActionCtx를 첫 번째 parameter로 받으므로 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을 정의하세요. 예를 들어 성공한 payment를 기록하려면
createPayment mutation을 생성하고, subscription state를 추적하려면 createSubscription mutation을 생성하세요. 이 예시에서는 해당 mutation이 convex/webhooks.ts에 있다고 가정합니다..convex.site domain에서 HTTP action을 제공하므로 URL은 https://<your-deployment-name>.convex.site/dodopayments-webhook입니다.Checkout Function
Convex component는 모든 payment에 권장되는 checkout flow인 checkout session을 생성합니다. session에는 product cart, customer details 및 checkout option이 포함됩니다.사용법
Convex action에서 checkout session field를payload에 전달하여 checkout를 호출하세요:
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 }를 전달하세요. 자세한 내용과 지원되는 field의 전체 목록은 Checkout Sessions을 참조하세요.
payment_method_id로 생성한 session은 checkout URL을 반환하지 않으므로 checkout는 이에 대해 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에게 email로도 전송합니다.customerPortal는 DodoPayments setup의 identify function에서 customer를 가져옵니다. 이 function은 customer의 dodoCustomerId를 반환해야 합니다. identify가 null를 반환하면 customerPortal는 User is not authenticated. error를 throw합니다.Webhook Handler
createDodoWebhookHandler는 code를 실행하기 전에 각 request를 검증합니다:
- Method:
method: "POST"로 route를 등록하세요. 다른 method의 request는 handler에 도달하지 않습니다. - Signature Verification:
DODO_PAYMENTS_WEBHOOK_SECRETenvironment variable로 Standard Webhooks signature를 검증합니다. 검증에 실패하면 400을 반환합니다. - Payload Validation: Zod로 validation합니다. 유효하지 않은 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를 호출합니다.
Supported Webhook Event Handlers
각 handler는 ConvexActionCtx와 해당 event type에 대해 검증된 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 사용법
convex/react의 useAction hook을 사용하여 React component에서 checkout 및 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
이 prompt를 AI coding assistant에 복사하여 project에 component를 추가하도록 하세요. 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.