@dodopayments/convex 0.1.1 → 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
@@ -58,48 +58,67 @@ Set the following variables:
58
58
 
59
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.
60
60
 
61
- ### 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
62
83
 
63
84
  ```typescript
64
85
  // convex/dodo.ts
65
86
  import { DodoPayments } from "@dodopayments/convex";
66
87
  import { components } from "./_generated/api";
88
+ import { internal } from "./_generated/api";
67
89
 
68
90
  export const dodo = new DodoPayments(components.dodopayments, {
69
91
  // This function maps your Convex user to a Dodo Payments customer
70
- // Customize it based on your authentication provider and/or user database
92
+ // Customize it based on your authentication provider and user database
93
+
71
94
  identify: async (ctx) => {
72
95
  const identity = await ctx.auth.getUserIdentity();
73
96
  if (!identity) {
74
97
  return null; // User is not logged in
75
98
  }
76
99
 
77
- // Lookup user from your database
78
- const user = await ctx.db.query("users")
79
- .withIndex("by_auth_id", (q) => q.eq("authId", identity.subject))
80
- .first();
81
- if (!user) {
82
- return null; // User not found in database
83
- }
84
-
85
- return {
86
- dodoCustomerId: user.dodoCustomerId, // Use stored dodoCustomerId
87
- customerData: {
88
- name: user.name,
89
- email: user.email,
90
- },
91
- };
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
+ };
92
112
  },
93
113
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
94
114
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
95
115
  });
96
116
 
97
- export const { checkout, customerPortal } = dodo.api();
98
117
  // Export the API methods for use in your app
99
118
  export const { checkout, customerPortal } = dodo.api();
100
119
  ```
101
120
 
102
- ### 4. Set Up Webhooks (Optional)
121
+ ### 5. Set Up Webhooks (Optional)
103
122
 
104
123
  For handling Dodo Payments webhooks, create a file `convex/http.ts`:
105
124
 
@@ -107,6 +126,7 @@ For handling Dodo Payments webhooks, create a file `convex/http.ts`:
107
126
  // convex/http.ts
108
127
  import { httpRouter } from "convex/server";
109
128
  import { createDodoWebhookHandler } from "@dodopayments/convex";
129
+ import { internal } from "./_generated/api";
110
130
 
111
131
  const http = httpRouter();
112
132
 
@@ -114,13 +134,24 @@ http.route({
114
134
  path: "/dodopayments-webhook",
115
135
  method: "POST",
116
136
  handler: createDodoWebhookHandler({
117
- onPaymentSucceeded: async (payload) => {
118
- console.log("Payment succeeded:", payload.data.payment_id);
119
- // 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
+ });
120
145
  },
121
- onSubscriptionActive: async (payload) => {
146
+
147
+ onSubscriptionActive: async (ctx, payload) => {
122
148
  console.log("Subscription activated:", payload.data.subscription_id);
123
- // 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
+ });
124
155
  },
125
156
  // Add other event handlers as needed
126
157
  }),
@@ -129,11 +160,13 @@ http.route({
129
160
  export default http;
130
161
  ```
131
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
+
132
165
  Add your webhook secret in the Convex dashboard (**Settings** → **Environment Variables**):
133
166
 
134
167
  - `DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret`
135
168
 
136
- ### 5. Define Payment Actions
169
+ ### 6. Define Payment Actions
137
170
 
138
171
  ```typescript
139
172
  // convex/payments.ts
@@ -164,7 +197,7 @@ export const createCheckout = action({
164
197
  });
165
198
  ```
166
199
 
167
- ### 6. Frontend Usage
200
+ ### 7. Frontend Usage
168
201
 
169
202
  ```tsx
170
203
  import { useAction } from "convex/react";
@@ -235,44 +268,51 @@ Then add the required environment variables (e.g., DODO_PAYMENTS_API_KEY, DODO_P
235
268
  DODO_PAYMENTS_API_KEY=your-api-key
236
269
  DODO_PAYMENTS_ENVIRONMENT=test_mode
237
270
 
238
- 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.
239
289
 
240
290
  // convex/dodo.ts
241
291
  import { DodoPayments } from "@dodopayments/convex";
242
292
  import { components } from "./_generated/api";
293
+ import { internal } from "./_generated/api";
243
294
 
244
295
  export const dodo = new DodoPayments(components.dodopayments, {
245
296
  // This function maps your Convex user to a Dodo Payments customer
246
- // Customize it based on your authentication provider
297
+ // Customize it based on your authentication provider and user database
298
+
247
299
  identify: async (ctx) => {
248
300
  const identity = await ctx.auth.getUserIdentity();
249
301
  if (!identity) {
250
302
  return null; // User is not logged in
251
303
  }
252
304
 
253
- // Option 1: Use identity data directly
254
- return {
255
- dodoCustomerId: identity.subject, // Use a stable identifier for the customer ID
256
- customerData: { // Optional: additional customer information
257
- name: identity.name,
258
- email: identity.email,
259
- },
260
- };
305
+ // Use ctx.runQuery() to lookup user from your database
306
+ const user = await ctx.runQuery(internal.users.getByAuthId, {
307
+ authId: identity.subject,
308
+ });
261
309
 
262
- // Lookup user from your database
263
- const user = await ctx.db.query("users")
264
- .withIndex("by_auth_id", (q) => q.eq("authId", identity.subject))
265
- .first();
266
310
  if (!user) {
267
311
  return null; // User not found in database
268
312
  }
269
-
313
+
270
314
  return {
271
- dodoCustomerId: user.dodoCustomerId, // Use stored dodoCustomerId
272
- customerData: {
273
- name: user.name,
274
- email: user.email,
275
- },
315
+ dodoCustomerId: user.dodoCustomerId, // Replace user.dodoCustomerId with your field storing Dodo Payments customer ID
276
316
  };
277
317
  },
278
318
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
@@ -282,7 +322,7 @@ export const dodo = new DodoPayments(components.dodopayments, {
282
322
  // Export the API methods for use in your app
283
323
  export const { checkout, customerPortal } = dodo.api();
284
324
 
285
- Step 4: Create actions that use the checkout function.
325
+ Step 5: Create actions that use the checkout function.
286
326
 
287
327
  // convex/payments.ts
288
328
  import { action } from "./_generated/server";
@@ -312,7 +352,7 @@ export const createCheckout = action({
312
352
  },
313
353
  });
314
354
 
315
- Step 5: Use in your frontend.
355
+ Step 6: Use in your frontend.
316
356
 
317
357
  // Your frontend component
318
358
  import { useAction } from "convex/react";
@@ -347,9 +387,9 @@ Purpose: This function allows customers to manage their subscriptions and paymen
347
387
 
348
388
  Integration Steps:
349
389
 
350
- Follow Steps 1-3 from the Checkout Function section, then:
390
+ Follow Steps 1-4 from the Checkout Function section, then:
351
391
 
352
- Step 4: Create a customer portal action.
392
+ Step 5: Create a customer portal action.
353
393
 
354
394
  // convex/payments.ts (add to existing file)
355
395
  import { action } from "./_generated/server";
@@ -365,7 +405,7 @@ export const getCustomerPortal = action({
365
405
  },
366
406
  });
367
407
 
368
- Step 5: Use in your frontend.
408
+ Step 6: Use in your frontend.
369
409
 
370
410
  // Your frontend component
371
411
  import { useAction } from "convex/react";
@@ -409,6 +449,7 @@ Step 2: Create a file `convex/http.ts`:
409
449
  // convex/http.ts
410
450
  import { httpRouter } from "convex/server";
411
451
  import { createDodoWebhookHandler } from "@dodopayments/convex";
452
+ import { internal } from "./_generated/api";
412
453
 
413
454
  const http = httpRouter();
414
455
 
@@ -416,13 +457,22 @@ http.route({
416
457
  path: "/dodopayments-webhook",
417
458
  method: "POST",
418
459
  handler: createDodoWebhookHandler({
419
- onPaymentSucceeded: async (payload) => {
460
+ onPaymentSucceeded: async (ctx, payload) => {
420
461
  console.log("Payment succeeded:", payload.data.payment_id);
421
- // 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
+ });
422
467
  },
423
- onSubscriptionActive: async (payload) => {
468
+
469
+ onSubscriptionActive: async (ctx, payload) => {
424
470
  console.log("Subscription activated:", payload.data.subscription_id);
425
- // 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
+ });
426
476
  },
427
477
  // Add other event handlers as needed
428
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";