@dodopayments/convex 0.1.1 → 0.2.1

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
@@ -22,7 +22,6 @@ npm install @dodopayments/convex
22
22
 
23
23
  ## Quick Start
24
24
 
25
-
26
25
  ### 1. Add Component to Convex Config
27
26
 
28
27
  ```typescript
@@ -46,6 +45,7 @@ DODO_PAYMENTS_API_KEY=your-dodo-payments-api-key
46
45
  DODO_PAYMENTS_ENVIRONMENT=test_mode
47
46
  DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret (if using webhooks)
48
47
  ```
48
+
49
49
  ```sh
50
50
  npx convex dashboard
51
51
  ```
@@ -58,55 +58,75 @@ 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
 
106
125
  ```typescript
107
126
  // convex/http.ts
108
- import { httpRouter } from "convex/server";
109
127
  import { createDodoWebhookHandler } from "@dodopayments/convex";
128
+ import { httpRouter } from "convex/server";
129
+ import { internal } from "./_generated/api";
110
130
 
111
131
  const http = httpRouter();
112
132
 
@@ -114,13 +134,32 @@ 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
+ // Handle successful payments
138
+ onPaymentSucceeded: async (ctx, payload) => {
139
+ console.log("🎉 Payment Succeeded!");
140
+ // Use Convex context to persist payment data
141
+ await ctx.runMutation(internal.webhooks.createPayment, {
142
+ paymentId: payload.data.payment_id,
143
+ businessId: payload.business_id,
144
+ customerEmail: payload.data.customer.email,
145
+ amount: payload.data.total_amount,
146
+ currency: payload.data.currency,
147
+ status: payload.data.status,
148
+ webhookPayload: JSON.stringify(payload),
149
+ });
120
150
  },
121
- onSubscriptionActive: async (payload) => {
122
- console.log("Subscription activated:", payload.data.subscription_id);
123
- // Add your logic here
151
+
152
+ // Handle subscription activation
153
+ onSubscriptionActive: async (ctx, payload) => {
154
+ console.log("🎉 Subscription Activated!");
155
+ // Use Convex context to persist subscription data
156
+ await ctx.runMutation(internal.webhooks.createSubscription, {
157
+ subscriptionId: payload.data.subscription_id,
158
+ businessId: payload.business_id,
159
+ customerEmail: payload.data.customer.email,
160
+ status: payload.data.status,
161
+ webhookPayload: JSON.stringify(payload),
162
+ });
124
163
  },
125
164
  // Add other event handlers as needed
126
165
  }),
@@ -129,11 +168,15 @@ http.route({
129
168
  export default http;
130
169
  ```
131
170
 
171
+ **Important:** 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.
172
+
173
+ **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.
174
+
132
175
  Add your webhook secret in the Convex dashboard (**Settings** → **Environment Variables**):
133
176
 
134
177
  - `DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret`
135
178
 
136
- ### 5. Define Payment Actions
179
+ ### 6. Define Payment Actions
137
180
 
138
181
  ```typescript
139
182
  // convex/payments.ts
@@ -164,7 +207,7 @@ export const createCheckout = action({
164
207
  });
165
208
  ```
166
209
 
167
- ### 6. Frontend Usage
210
+ ### 7. Frontend Usage
168
211
 
169
212
  ```tsx
170
213
  import { useAction } from "convex/react";
@@ -235,44 +278,51 @@ Then add the required environment variables (e.g., DODO_PAYMENTS_API_KEY, DODO_P
235
278
  DODO_PAYMENTS_API_KEY=your-api-key
236
279
  DODO_PAYMENTS_ENVIRONMENT=test_mode
237
280
 
238
- Step 3: Create your payment functions file.
281
+ Step 3: Create an internal query to fetch users from your database.
282
+
283
+ // convex/users.ts
284
+ import { internalQuery } from "./_generated/server";
285
+ import { v } from "convex/values";
286
+
287
+ // Internal query to fetch user by auth ID
288
+ export const getByAuthId = internalQuery({
289
+ args: { authId: v.string() },
290
+ handler: async (ctx, { authId }) => {
291
+ return await ctx.db
292
+ .query("users")
293
+ .withIndex("by_auth_id", (q) => q.eq("authId", authId))
294
+ .first();
295
+ },
296
+ });
297
+
298
+ Step 4: Create your payment functions file.
239
299
 
240
300
  // convex/dodo.ts
241
301
  import { DodoPayments } from "@dodopayments/convex";
242
302
  import { components } from "./_generated/api";
303
+ import { internal } from "./_generated/api";
243
304
 
244
305
  export const dodo = new DodoPayments(components.dodopayments, {
245
306
  // This function maps your Convex user to a Dodo Payments customer
246
- // Customize it based on your authentication provider
307
+ // Customize it based on your authentication provider and user database
308
+
247
309
  identify: async (ctx) => {
248
310
  const identity = await ctx.auth.getUserIdentity();
249
311
  if (!identity) {
250
312
  return null; // User is not logged in
251
313
  }
252
314
 
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
- };
315
+ // Use ctx.runQuery() to lookup user from your database
316
+ const user = await ctx.runQuery(internal.users.getByAuthId, {
317
+ authId: identity.subject,
318
+ });
261
319
 
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
320
  if (!user) {
267
321
  return null; // User not found in database
268
322
  }
269
-
323
+
270
324
  return {
271
- dodoCustomerId: user.dodoCustomerId, // Use stored dodoCustomerId
272
- customerData: {
273
- name: user.name,
274
- email: user.email,
275
- },
325
+ dodoCustomerId: user.dodoCustomerId, // Replace user.dodoCustomerId with your field storing Dodo Payments customer ID
276
326
  };
277
327
  },
278
328
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
@@ -282,7 +332,7 @@ export const dodo = new DodoPayments(components.dodopayments, {
282
332
  // Export the API methods for use in your app
283
333
  export const { checkout, customerPortal } = dodo.api();
284
334
 
285
- Step 4: Create actions that use the checkout function.
335
+ Step 5: Create actions that use the checkout function.
286
336
 
287
337
  // convex/payments.ts
288
338
  import { action } from "./_generated/server";
@@ -312,7 +362,7 @@ export const createCheckout = action({
312
362
  },
313
363
  });
314
364
 
315
- Step 5: Use in your frontend.
365
+ Step 6: Use in your frontend.
316
366
 
317
367
  // Your frontend component
318
368
  import { useAction } from "convex/react";
@@ -347,9 +397,9 @@ Purpose: This function allows customers to manage their subscriptions and paymen
347
397
 
348
398
  Integration Steps:
349
399
 
350
- Follow Steps 1-3 from the Checkout Function section, then:
400
+ Follow Steps 1-4 from the Checkout Function section, then:
351
401
 
352
- Step 4: Create a customer portal action.
402
+ Step 5: Create a customer portal action.
353
403
 
354
404
  // convex/payments.ts (add to existing file)
355
405
  import { action } from "./_generated/server";
@@ -365,7 +415,7 @@ export const getCustomerPortal = action({
365
415
  },
366
416
  });
367
417
 
368
- Step 5: Use in your frontend.
418
+ Step 6: Use in your frontend.
369
419
 
370
420
  // Your frontend component
371
421
  import { useAction } from "convex/react";
@@ -407,8 +457,9 @@ Do not use .env files for backend functions; always set secrets in the Convex da
407
457
  Step 2: Create a file `convex/http.ts`:
408
458
 
409
459
  // convex/http.ts
410
- import { httpRouter } from "convex/server";
411
460
  import { createDodoWebhookHandler } from "@dodopayments/convex";
461
+ import { httpRouter } from "convex/server";
462
+ import { internal } from "./_generated/api";
412
463
 
413
464
  const http = httpRouter();
414
465
 
@@ -416,13 +467,32 @@ http.route({
416
467
  path: "/dodopayments-webhook",
417
468
  method: "POST",
418
469
  handler: createDodoWebhookHandler({
419
- onPaymentSucceeded: async (payload) => {
420
- console.log("Payment succeeded:", payload.data.payment_id);
421
- // Add your logic here to handle the successful payment
470
+ // Handle successful payments
471
+ onPaymentSucceeded: async (ctx, payload) => {
472
+ console.log("🎉 Payment Succeeded!");
473
+ // Use Convex context to persist payment data
474
+ await ctx.runMutation(internal.webhooks.createPayment, {
475
+ paymentId: payload.data.payment_id,
476
+ businessId: payload.business_id,
477
+ customerEmail: payload.data.customer.email,
478
+ amount: payload.data.total_amount,
479
+ currency: payload.data.currency,
480
+ status: payload.data.status,
481
+ webhookPayload: JSON.stringify(payload),
482
+ });
422
483
  },
423
- onSubscriptionActive: async (payload) => {
424
- console.log("Subscription activated:", payload.data.subscription_id);
425
- // Add your logic here
484
+
485
+ // Handle subscription activation
486
+ onSubscriptionActive: async (ctx, payload) => {
487
+ console.log("🎉 Subscription Activated!");
488
+ // Use Convex context to persist subscription data
489
+ await ctx.runMutation(internal.webhooks.createSubscription, {
490
+ subscriptionId: payload.data.subscription_id,
491
+ businessId: payload.business_id,
492
+ customerEmail: payload.data.customer.email,
493
+ status: payload.data.status,
494
+ webhookPayload: JSON.stringify(payload),
495
+ });
426
496
  },
427
497
  // Add other event handlers as needed
428
498
  }),
@@ -430,6 +500,8 @@ http.route({
430
500
 
431
501
  export default http;
432
502
 
503
+ 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.
504
+
433
505
  Now, you can set the webhook endpoint URL in your Dodo Payments dashboard to `https://<your-convex-deployment-url>/dodopayments-webhook`.
434
506
 
435
507
  Environment Variable Setup:
@@ -455,4 +527,3 @@ If the user needs assistance setting up environment variables or deployment, ask
455
527
 
456
528
  Run `npx convex dev` after setting up the component to generate the necessary types.
457
529
  ```
458
-
@@ -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";