@dodopayments/convex 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,16 @@
1
- # `@dodopayments/convex`
1
+ # Dodo Payments Convex Component
2
2
 
3
- A TypeScript library that provides a Convex component for seamless integration of Dodo Payments into your Convex applications.
3
+ [![npm version](https://badge.fury.io/js/@dodopayments%2Fconvex.svg)](https://badge.fury.io/js/@dodopayments%2Fconvex.svg)
4
4
 
5
- > **AI Agent Integration Guide:** See the AI Agent Prompt section below for detailed instructions and guidance for AI assistants.
5
+ This component is the official way to integrate the Dodo Payments in your Convex project.
6
6
 
7
- ## Documentation
7
+ Features:
8
+
9
+ - Checkout Session: Integrate Dodo Payments Checkout with a couple of lines code.
10
+
11
+ - Customer Portal: Allow customers to manage subscriptions and details
12
+
13
+ - Webhooks: Handle webhooks efficiently. Just write your business logic and handle the events you want. Webhook verification is taken care by us
8
14
 
9
15
  Detailed documentation can be found at [Dodo Payments Convex Component](https://docs.dodopayments.com/developer-resources/convex-component)
10
16
 
@@ -16,6 +22,7 @@ npm install @dodopayments/convex
16
22
 
17
23
  ## Quick Start
18
24
 
25
+
19
26
  ### 1. Add Component to Convex Config
20
27
 
21
28
  ```typescript
@@ -30,8 +37,15 @@ export default app;
30
37
 
31
38
  ### 2. Set Up Environment Variables
32
39
 
40
+ Create a [Dodo Payments](https://dodopayments.com) account and get the API_KEY and WEBHOOK_SECRET
41
+
33
42
  Add your environment variables in the Convex dashboard (**Settings** → **Environment Variables**). You can open the dashboard by running:
34
43
 
44
+ ```env
45
+ DODO_PAYMENTS_API_KEY=your-dodo-payments-api-key
46
+ DODO_PAYMENTS_ENVIRONMENT=test_mode
47
+ DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret (if using webhooks)
48
+ ```
35
49
  ```sh
36
50
  npx convex dashboard
37
51
  ```
@@ -44,37 +58,57 @@ Set the following variables:
44
58
 
45
59
  > **Note:** Convex backend functions only read environment variables set in the Convex dashboard. `.env` files are ignored. Always set secrets in the Convex dashboard for both production and development.
46
60
 
47
- ### 3. Create Payment Functions
61
+ ### 3. Create Internal Query
62
+
63
+ First, create an internal query to fetch users from your database. This will be used in the payment functions to identify customers.
64
+
65
+ ```typescript
66
+ // convex/users.ts
67
+ import { internalQuery } from "./_generated/server";
68
+ import { v } from "convex/values";
69
+
70
+ // Internal query to fetch user by auth ID
71
+ export const getByAuthId = internalQuery({
72
+ args: { authId: v.string() },
73
+ handler: async (ctx, { authId }) => {
74
+ return await ctx.db
75
+ .query("users")
76
+ .withIndex("by_auth_id", (q) => q.eq("authId", authId))
77
+ .first();
78
+ },
79
+ });
80
+ ```
81
+
82
+ ### 4. Create Payment Functions
48
83
 
49
84
  ```typescript
50
85
  // convex/dodo.ts
51
86
  import { DodoPayments } from "@dodopayments/convex";
52
87
  import { components } from "./_generated/api";
88
+ import { internal } from "./_generated/api";
53
89
 
54
90
  export const dodo = new DodoPayments(components.dodopayments, {
55
91
  // This function maps your Convex user to a Dodo Payments customer
56
- // Customize it based on your authentication provider and/or user database
92
+ // Customize it based on your authentication provider and user database
93
+
57
94
  identify: async (ctx) => {
58
95
  const identity = await ctx.auth.getUserIdentity();
59
96
  if (!identity) {
60
97
  return null; // User is not logged in
61
98
  }
62
99
 
63
- // Lookup user from your database
64
- const user = await ctx.db.query("users")
65
- .withIndex("by_auth_id", (q) => q.eq("authId", identity.subject))
66
- .first();
67
- if (!user) {
68
- return null; // User not found in database
69
- }
70
-
71
- return {
72
- dodoCustomerId: user.dodoCustomerId, // Use stored dodoCustomerId
73
- customerData: {
74
- name: user.name,
75
- email: user.email,
76
- },
77
- };
100
+ // Use ctx.runQuery() to lookup user from your database
101
+ const user = await ctx.runQuery(internal.users.getByAuthId, {
102
+ authId: identity.subject,
103
+ });
104
+
105
+ if (!user) {
106
+ return null; // User not found in database
107
+ }
108
+
109
+ return {
110
+ dodoCustomerId: user.dodoCustomerId, // Replace user.dodoCustomerId with your field storing Dodo Payments customer ID
111
+ };
78
112
  },
79
113
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
80
114
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
@@ -84,7 +118,7 @@ export const dodo = new DodoPayments(components.dodopayments, {
84
118
  export const { checkout, customerPortal } = dodo.api();
85
119
  ```
86
120
 
87
- ### 4. Set Up Webhooks (Optional)
121
+ ### 5. Set Up Webhooks (Optional)
88
122
 
89
123
  For handling Dodo Payments webhooks, create a file `convex/http.ts`:
90
124
 
@@ -92,6 +126,7 @@ For handling Dodo Payments webhooks, create a file `convex/http.ts`:
92
126
  // convex/http.ts
93
127
  import { httpRouter } from "convex/server";
94
128
  import { createDodoWebhookHandler } from "@dodopayments/convex";
129
+ import { internal } from "./_generated/api";
95
130
 
96
131
  const http = httpRouter();
97
132
 
@@ -99,13 +134,24 @@ http.route({
99
134
  path: "/dodopayments-webhook",
100
135
  method: "POST",
101
136
  handler: createDodoWebhookHandler({
102
- onPaymentSucceeded: async (payload) => {
103
- console.log("Payment succeeded:", payload.data.payment_id);
104
- // Add your logic here to handle the successful payment
137
+ onPaymentSucceeded: async (ctx, payload) => {
138
+ console.log("Payment succeeded:", payload.data.payment_id);
139
+ // Update order status in your database
140
+ await ctx.runMutation(internal.orders.markAsPaid, {
141
+ orderId: payload.data.metadata.orderId,
142
+ paymentId: payload.data.payment_id,
143
+ amount: payload.data.amount,
144
+ });
105
145
  },
106
- onSubscriptionActive: async (payload) => {
146
+
147
+ onSubscriptionActive: async (ctx, payload) => {
107
148
  console.log("Subscription activated:", payload.data.subscription_id);
108
- // Add your logic here
149
+ // Create or update subscription record in your database
150
+ await ctx.runMutation(internal.subscriptions.createOrUpdate, {
151
+ subscriptionId: payload.data.subscription_id,
152
+ customerId: payload.data.customer_id,
153
+ status: "active",
154
+ });
109
155
  },
110
156
  // Add other event handlers as needed
111
157
  }),
@@ -114,11 +160,13 @@ http.route({
114
160
  export default http;
115
161
  ```
116
162
 
163
+ **Important:** All webhook handlers receive the Convex `ActionCtx` as the first parameter, allowing you to use `ctx.runQuery()` and `ctx.runMutation()` to interact with your database.
164
+
117
165
  Add your webhook secret in the Convex dashboard (**Settings** → **Environment Variables**):
118
166
 
119
167
  - `DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret`
120
168
 
121
- ### 5. Define Payment Actions
169
+ ### 6. Define Payment Actions
122
170
 
123
171
  ```typescript
124
172
  // convex/payments.ts
@@ -149,7 +197,7 @@ export const createCheckout = action({
149
197
  });
150
198
  ```
151
199
 
152
- ### 6. Frontend Usage
200
+ ### 7. Frontend Usage
153
201
 
154
202
  ```tsx
155
203
  import { useAction } from "convex/react";
@@ -220,44 +268,51 @@ Then add the required environment variables (e.g., DODO_PAYMENTS_API_KEY, DODO_P
220
268
  DODO_PAYMENTS_API_KEY=your-api-key
221
269
  DODO_PAYMENTS_ENVIRONMENT=test_mode
222
270
 
223
- Step 3: Create your payment functions file.
271
+ Step 3: Create an internal query to fetch users from your database.
272
+
273
+ // convex/users.ts
274
+ import { internalQuery } from "./_generated/server";
275
+ import { v } from "convex/values";
276
+
277
+ // Internal query to fetch user by auth ID
278
+ export const getByAuthId = internalQuery({
279
+ args: { authId: v.string() },
280
+ handler: async (ctx, { authId }) => {
281
+ return await ctx.db
282
+ .query("users")
283
+ .withIndex("by_auth_id", (q) => q.eq("authId", authId))
284
+ .first();
285
+ },
286
+ });
287
+
288
+ Step 4: Create your payment functions file.
224
289
 
225
290
  // convex/dodo.ts
226
291
  import { DodoPayments } from "@dodopayments/convex";
227
292
  import { components } from "./_generated/api";
293
+ import { internal } from "./_generated/api";
228
294
 
229
295
  export const dodo = new DodoPayments(components.dodopayments, {
230
296
  // This function maps your Convex user to a Dodo Payments customer
231
- // Customize it based on your authentication provider
297
+ // Customize it based on your authentication provider and user database
298
+
232
299
  identify: async (ctx) => {
233
300
  const identity = await ctx.auth.getUserIdentity();
234
301
  if (!identity) {
235
302
  return null; // User is not logged in
236
303
  }
237
304
 
238
- // Option 1: Use identity data directly
239
- return {
240
- dodoCustomerId: identity.subject, // Use a stable identifier for the customer ID
241
- customerData: { // Optional: additional customer information
242
- name: identity.name,
243
- email: identity.email,
244
- },
245
- };
305
+ // Use ctx.runQuery() to lookup user from your database
306
+ const user = await ctx.runQuery(internal.users.getByAuthId, {
307
+ authId: identity.subject,
308
+ });
246
309
 
247
- // Lookup user from your database
248
- const user = await ctx.db.query("users")
249
- .withIndex("by_auth_id", (q) => q.eq("authId", identity.subject))
250
- .first();
251
310
  if (!user) {
252
311
  return null; // User not found in database
253
312
  }
254
-
313
+
255
314
  return {
256
- dodoCustomerId: user.dodoCustomerId, // Use stored dodoCustomerId
257
- customerData: {
258
- name: user.name,
259
- email: user.email,
260
- },
315
+ dodoCustomerId: user.dodoCustomerId, // Replace user.dodoCustomerId with your field storing Dodo Payments customer ID
261
316
  };
262
317
  },
263
318
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
@@ -267,7 +322,7 @@ export const dodo = new DodoPayments(components.dodopayments, {
267
322
  // Export the API methods for use in your app
268
323
  export const { checkout, customerPortal } = dodo.api();
269
324
 
270
- Step 4: Create actions that use the checkout function.
325
+ Step 5: Create actions that use the checkout function.
271
326
 
272
327
  // convex/payments.ts
273
328
  import { action } from "./_generated/server";
@@ -297,7 +352,7 @@ export const createCheckout = action({
297
352
  },
298
353
  });
299
354
 
300
- Step 5: Use in your frontend.
355
+ Step 6: Use in your frontend.
301
356
 
302
357
  // Your frontend component
303
358
  import { useAction } from "convex/react";
@@ -332,9 +387,9 @@ Purpose: This function allows customers to manage their subscriptions and paymen
332
387
 
333
388
  Integration Steps:
334
389
 
335
- Follow Steps 1-3 from the Checkout Function section, then:
390
+ Follow Steps 1-4 from the Checkout Function section, then:
336
391
 
337
- Step 4: Create a customer portal action.
392
+ Step 5: Create a customer portal action.
338
393
 
339
394
  // convex/payments.ts (add to existing file)
340
395
  import { action } from "./_generated/server";
@@ -350,7 +405,7 @@ export const getCustomerPortal = action({
350
405
  },
351
406
  });
352
407
 
353
- Step 5: Use in your frontend.
408
+ Step 6: Use in your frontend.
354
409
 
355
410
  // Your frontend component
356
411
  import { useAction } from "convex/react";
@@ -394,6 +449,7 @@ Step 2: Create a file `convex/http.ts`:
394
449
  // convex/http.ts
395
450
  import { httpRouter } from "convex/server";
396
451
  import { createDodoWebhookHandler } from "@dodopayments/convex";
452
+ import { internal } from "./_generated/api";
397
453
 
398
454
  const http = httpRouter();
399
455
 
@@ -401,13 +457,22 @@ http.route({
401
457
  path: "/dodopayments-webhook",
402
458
  method: "POST",
403
459
  handler: createDodoWebhookHandler({
404
- onPaymentSucceeded: async (payload) => {
460
+ onPaymentSucceeded: async (ctx, payload) => {
405
461
  console.log("Payment succeeded:", payload.data.payment_id);
406
- // Add your logic here to handle the successful payment
462
+ // Update order status in your database
463
+ await ctx.runMutation(internal.orders.markAsPaid, {
464
+ orderId: payload.data.metadata.orderId,
465
+ paymentId: payload.data.payment_id,
466
+ });
407
467
  },
408
- onSubscriptionActive: async (payload) => {
468
+
469
+ onSubscriptionActive: async (ctx, payload) => {
409
470
  console.log("Subscription activated:", payload.data.subscription_id);
410
- // Add your logic here
471
+ // Use ctx to create or update subscription records
472
+ await ctx.runMutation(internal.subscriptions.createOrUpdate, {
473
+ subscriptionId: payload.data.subscription_id,
474
+ customerId: payload.data.customer_id,
475
+ });
411
476
  },
412
477
  // Add other event handlers as needed
413
478
  }),
@@ -12,7 +12,6 @@ export interface DodoPaymentsComponent {
12
12
  export type DodoPaymentsClientConfig = {
13
13
  identify: (ctx: GenericActionCtx<any>) => Promise<{
14
14
  dodoCustomerId: string;
15
- customerData?: any;
16
15
  } | null>;
17
16
  apiKey: string;
18
17
  environment: "test_mode" | "live_mode";