convex-affiliates 1.1.3 → 2.0.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 +10 -2
- package/dist/client/index.d.ts +45 -44
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +162 -159
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +50 -50
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/client/index.test.ts +11 -0
- package/src/client/index.ts +191 -193
- package/src/component/_generated/component.ts +50 -50
package/README.md
CHANGED
|
@@ -255,14 +255,22 @@ If you're not using `@convex-dev/stripe`, use the standalone handler with built-
|
|
|
255
255
|
// convex/http.ts
|
|
256
256
|
import { httpRouter } from "convex/server";
|
|
257
257
|
import { components } from "./_generated/api";
|
|
258
|
-
import {
|
|
258
|
+
import { createAffiliateApi } from "convex-affiliates";
|
|
259
259
|
|
|
260
260
|
const http = httpRouter();
|
|
261
261
|
|
|
262
|
+
const affiliates = createAffiliateApi(components.affiliates, {
|
|
263
|
+
auth: async (ctx) => {
|
|
264
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
265
|
+
if (!identity) throw new Error("Not authenticated");
|
|
266
|
+
return identity.subject;
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
262
270
|
http.route({
|
|
263
271
|
path: "/webhooks/stripe",
|
|
264
272
|
method: "POST",
|
|
265
|
-
handler: createStripeWebhookHandler(
|
|
273
|
+
handler: affiliates.createStripeWebhookHandler({
|
|
266
274
|
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
267
275
|
}),
|
|
268
276
|
});
|
package/dist/client/index.d.ts
CHANGED
|
@@ -168,19 +168,14 @@ export interface CreateAffiliateApiConfig extends AffiliateConfig {
|
|
|
168
168
|
* import { createAffiliateApi } from "convex-affiliates";
|
|
169
169
|
* import { components } from "./_generated/api";
|
|
170
170
|
*
|
|
171
|
-
* export const {
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* const identity = await ctx.auth.getUserIdentity();
|
|
180
|
-
* if (!identity) throw new Error("Not authenticated");
|
|
181
|
-
* return identity.subject;
|
|
182
|
-
* },
|
|
183
|
-
* });
|
|
171
|
+
* export const { trackClick, register, getPortalData } =
|
|
172
|
+
* createAffiliateApi(components.affiliates, {
|
|
173
|
+
* auth: async (ctx) => {
|
|
174
|
+
* const identity = await ctx.auth.getUserIdentity();
|
|
175
|
+
* if (!identity) throw new Error("Not authenticated");
|
|
176
|
+
* return identity.subject;
|
|
177
|
+
* },
|
|
178
|
+
* });
|
|
184
179
|
* ```
|
|
185
180
|
*/
|
|
186
181
|
export declare function createAffiliateApi(component: ComponentApi, config: CreateAffiliateApiConfig): {
|
|
@@ -950,14 +945,44 @@ export declare function createAffiliateApi(component: ComponentApi, config: Crea
|
|
|
950
945
|
slug: string;
|
|
951
946
|
commissionValue: number;
|
|
952
947
|
}, Promise<import("../component/_generated/dataModel.js").Id<"campaigns">>>;
|
|
948
|
+
/**
|
|
949
|
+
* Register HTTP routes for affiliate functionality.
|
|
950
|
+
* The component reference is already available from the closure.
|
|
951
|
+
*
|
|
952
|
+
* @param http - The Convex HTTP router
|
|
953
|
+
* @param options - Optional configuration (e.g., pathPrefix)
|
|
954
|
+
*
|
|
955
|
+
* @example
|
|
956
|
+
* ```typescript
|
|
957
|
+
* const affiliates = createAffiliateApi(components.affiliates, { ... });
|
|
958
|
+
* affiliates.registerRoutes(http, { pathPrefix: "/affiliates" });
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
961
|
+
registerRoutes(http: HttpRouter, options?: {
|
|
962
|
+
pathPrefix?: string;
|
|
963
|
+
}): void;
|
|
964
|
+
/**
|
|
965
|
+
* Create a Stripe webhook handler that processes affiliate-related events.
|
|
966
|
+
* The component reference is already available from the closure.
|
|
967
|
+
*
|
|
968
|
+
* @param webhookConfig - Configuration with webhookSecret
|
|
969
|
+
* @returns An HTTP action handler for Stripe webhooks
|
|
970
|
+
*
|
|
971
|
+
* @example
|
|
972
|
+
* ```typescript
|
|
973
|
+
* const affiliates = createAffiliateApi(components.affiliates, { ... });
|
|
974
|
+
*
|
|
975
|
+
* http.route({
|
|
976
|
+
* path: "/webhooks/stripe",
|
|
977
|
+
* method: "POST",
|
|
978
|
+
* handler: affiliates.createStripeWebhookHandler({
|
|
979
|
+
* webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
980
|
+
* }),
|
|
981
|
+
* });
|
|
982
|
+
* ```
|
|
983
|
+
*/
|
|
984
|
+
createStripeWebhookHandler(webhookConfig: StripeWebhookConfig): import("convex/server").PublicHttpAction;
|
|
953
985
|
};
|
|
954
|
-
/**
|
|
955
|
-
* Register HTTP routes for affiliate functionality.
|
|
956
|
-
* Useful for public API endpoints and webhook handling.
|
|
957
|
-
*/
|
|
958
|
-
export declare function registerRoutes(http: HttpRouter, component: ComponentApi, options?: {
|
|
959
|
-
pathPrefix?: string;
|
|
960
|
-
}): void;
|
|
961
986
|
export interface StripeWebhookConfig {
|
|
962
987
|
/**
|
|
963
988
|
* Stripe webhook signing secret (whsec_...).
|
|
@@ -966,30 +991,6 @@ export interface StripeWebhookConfig {
|
|
|
966
991
|
*/
|
|
967
992
|
webhookSecret: string;
|
|
968
993
|
}
|
|
969
|
-
/**
|
|
970
|
-
* Create a Stripe webhook handler that processes affiliate-related events.
|
|
971
|
-
* Handles invoice.paid, charge.refunded, and checkout.session.completed events.
|
|
972
|
-
*
|
|
973
|
-
* @example
|
|
974
|
-
* ```typescript
|
|
975
|
-
* import { httpRouter } from "convex/server";
|
|
976
|
-
* import { createStripeWebhookHandler } from "convex-affiliates";
|
|
977
|
-
* import { components } from "./_generated/api";
|
|
978
|
-
*
|
|
979
|
-
* const http = httpRouter();
|
|
980
|
-
*
|
|
981
|
-
* http.route({
|
|
982
|
-
* path: "/webhooks/stripe",
|
|
983
|
-
* method: "POST",
|
|
984
|
-
* handler: createStripeWebhookHandler(components.affiliates, {
|
|
985
|
-
* webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
986
|
-
* }),
|
|
987
|
-
* });
|
|
988
|
-
*
|
|
989
|
-
* export default http;
|
|
990
|
-
* ```
|
|
991
|
-
*/
|
|
992
|
-
export declare function createStripeWebhookHandler(component: ComponentApi, config: StripeWebhookConfig): import("convex/server").PublicHttpAction;
|
|
993
994
|
/**
|
|
994
995
|
* Options for Stripe event handlers.
|
|
995
996
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.js
CHANGED
|
@@ -14,22 +14,20 @@ import { affiliateStatusValidator, payoutTermValidator, commissionTypeValidator,
|
|
|
14
14
|
* import { createAffiliateApi } from "convex-affiliates";
|
|
15
15
|
* import { components } from "./_generated/api";
|
|
16
16
|
*
|
|
17
|
-
* export const {
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* const identity = await ctx.auth.getUserIdentity();
|
|
26
|
-
* if (!identity) throw new Error("Not authenticated");
|
|
27
|
-
* return identity.subject;
|
|
28
|
-
* },
|
|
29
|
-
* });
|
|
17
|
+
* export const { trackClick, register, getPortalData } =
|
|
18
|
+
* createAffiliateApi(components.affiliates, {
|
|
19
|
+
* auth: async (ctx) => {
|
|
20
|
+
* const identity = await ctx.auth.getUserIdentity();
|
|
21
|
+
* if (!identity) throw new Error("Not authenticated");
|
|
22
|
+
* return identity.subject;
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
30
25
|
* ```
|
|
31
26
|
*/
|
|
32
27
|
export function createAffiliateApi(component, config) {
|
|
28
|
+
const auth = config.auth;
|
|
29
|
+
const isAdmin = config.isAdmin;
|
|
30
|
+
const hooks = config.hooks;
|
|
33
31
|
const defaults = {
|
|
34
32
|
defaultCommissionType: config.defaultCommissionType ?? "percentage",
|
|
35
33
|
defaultCommissionValue: config.defaultCommissionValue ?? 20,
|
|
@@ -57,14 +55,14 @@ export function createAffiliateApi(component, config) {
|
|
|
57
55
|
};
|
|
58
56
|
// Helper to check admin access
|
|
59
57
|
const requireAdmin = async (ctx) => {
|
|
60
|
-
if (
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
58
|
+
if (isAdmin) {
|
|
59
|
+
const isAdminResult = await isAdmin(ctx);
|
|
60
|
+
if (!isAdminResult)
|
|
63
61
|
throw new Error("Not authorized - admin access required");
|
|
64
62
|
}
|
|
65
63
|
else {
|
|
66
64
|
// Fall back to just requiring auth
|
|
67
|
-
await
|
|
65
|
+
await auth(ctx);
|
|
68
66
|
}
|
|
69
67
|
};
|
|
70
68
|
// Helper to get affiliate by userId
|
|
@@ -73,7 +71,7 @@ export function createAffiliateApi(component, config) {
|
|
|
73
71
|
};
|
|
74
72
|
// Helper to call lifecycle hooks safely (errors are logged, not thrown)
|
|
75
73
|
async function callHook(hookName, data) {
|
|
76
|
-
const hook =
|
|
74
|
+
const hook = hooks?.[hookName];
|
|
77
75
|
if (hook) {
|
|
78
76
|
try {
|
|
79
77
|
await hook(data);
|
|
@@ -201,7 +199,7 @@ export function createAffiliateApi(component, config) {
|
|
|
201
199
|
customCode: v.optional(v.string()),
|
|
202
200
|
},
|
|
203
201
|
handler: async (ctx, args) => {
|
|
204
|
-
const userId = await
|
|
202
|
+
const userId = await auth(ctx);
|
|
205
203
|
const campaign = await ensureDefaultCampaign(ctx);
|
|
206
204
|
const result = await ctx.runMutation(component.affiliates.register, {
|
|
207
205
|
userId,
|
|
@@ -243,7 +241,7 @@ export function createAffiliateApi(component, config) {
|
|
|
243
241
|
getAffiliate: queryGeneric({
|
|
244
242
|
args: {},
|
|
245
243
|
handler: async (ctx) => {
|
|
246
|
-
const userId = await
|
|
244
|
+
const userId = await auth(ctx);
|
|
247
245
|
return ctx.runQuery(component.affiliates.getByUserId, { userId });
|
|
248
246
|
},
|
|
249
247
|
}),
|
|
@@ -280,7 +278,7 @@ export function createAffiliateApi(component, config) {
|
|
|
280
278
|
payoutEmail: v.optional(v.string()),
|
|
281
279
|
},
|
|
282
280
|
handler: async (ctx, args) => {
|
|
283
|
-
const userId = await
|
|
281
|
+
const userId = await auth(ctx);
|
|
284
282
|
const affiliate = await ctx.runQuery(component.affiliates.getByUserId, {
|
|
285
283
|
userId,
|
|
286
284
|
});
|
|
@@ -313,7 +311,7 @@ export function createAffiliateApi(component, config) {
|
|
|
313
311
|
getPortalData: queryGeneric({
|
|
314
312
|
args: {},
|
|
315
313
|
handler: async (ctx) => {
|
|
316
|
-
const userId = await
|
|
314
|
+
const userId = await auth(ctx);
|
|
317
315
|
return ctx.runQuery(component.analytics.getPortalData, { userId });
|
|
318
316
|
},
|
|
319
317
|
}),
|
|
@@ -345,7 +343,7 @@ export function createAffiliateApi(component, config) {
|
|
|
345
343
|
paginationOpts: paginationOptsValidator,
|
|
346
344
|
},
|
|
347
345
|
handler: async (ctx, args) => {
|
|
348
|
-
const userId = await
|
|
346
|
+
const userId = await auth(ctx);
|
|
349
347
|
const affiliate = await getAffiliateByUserId(ctx, userId);
|
|
350
348
|
if (!affiliate) {
|
|
351
349
|
return { page: [], isDone: true, continueCursor: "" };
|
|
@@ -384,7 +382,7 @@ export function createAffiliateApi(component, config) {
|
|
|
384
382
|
paginationOpts: paginationOptsValidator,
|
|
385
383
|
},
|
|
386
384
|
handler: async (ctx, args) => {
|
|
387
|
-
const userId = await
|
|
385
|
+
const userId = await auth(ctx);
|
|
388
386
|
const affiliate = await getAffiliateByUserId(ctx, userId);
|
|
389
387
|
if (!affiliate) {
|
|
390
388
|
return { page: [], isDone: true, continueCursor: "" };
|
|
@@ -421,7 +419,7 @@ export function createAffiliateApi(component, config) {
|
|
|
421
419
|
limit: v.optional(v.number()),
|
|
422
420
|
},
|
|
423
421
|
handler: async (ctx, args) => {
|
|
424
|
-
const userId = await
|
|
422
|
+
const userId = await auth(ctx);
|
|
425
423
|
const affiliate = await getAffiliateByUserId(ctx, userId);
|
|
426
424
|
if (!affiliate) {
|
|
427
425
|
return [];
|
|
@@ -465,7 +463,7 @@ export function createAffiliateApi(component, config) {
|
|
|
465
463
|
subId: v.optional(v.string()),
|
|
466
464
|
},
|
|
467
465
|
handler: async (ctx, args) => {
|
|
468
|
-
const userId = await
|
|
466
|
+
const userId = await auth(ctx);
|
|
469
467
|
const affiliate = await ctx.runQuery(component.affiliates.getByUserId, {
|
|
470
468
|
userId,
|
|
471
469
|
});
|
|
@@ -857,52 +855,149 @@ export function createAffiliateApi(component, config) {
|
|
|
857
855
|
});
|
|
858
856
|
},
|
|
859
857
|
}),
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
//
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
858
|
+
// =========================================================================
|
|
859
|
+
// HTTP Routes
|
|
860
|
+
// =========================================================================
|
|
861
|
+
/**
|
|
862
|
+
* Register HTTP routes for affiliate functionality.
|
|
863
|
+
* The component reference is already available from the closure.
|
|
864
|
+
*
|
|
865
|
+
* @param http - The Convex HTTP router
|
|
866
|
+
* @param options - Optional configuration (e.g., pathPrefix)
|
|
867
|
+
*
|
|
868
|
+
* @example
|
|
869
|
+
* ```typescript
|
|
870
|
+
* const affiliates = createAffiliateApi(components.affiliates, { ... });
|
|
871
|
+
* affiliates.registerRoutes(http, { pathPrefix: "/affiliates" });
|
|
872
|
+
* ```
|
|
873
|
+
*/
|
|
874
|
+
registerRoutes(http, options = {}) {
|
|
875
|
+
const prefix = options.pathPrefix ?? "/affiliates";
|
|
876
|
+
http.route({
|
|
877
|
+
path: `${prefix}/affiliate/:code`,
|
|
878
|
+
method: "GET",
|
|
879
|
+
handler: httpActionGeneric(async (ctx, request) => {
|
|
880
|
+
const url = new URL(request.url);
|
|
881
|
+
const code = url.pathname.split("/").pop();
|
|
882
|
+
if (!code) {
|
|
883
|
+
return new Response(JSON.stringify({ error: "Code required" }), {
|
|
884
|
+
status: 400,
|
|
885
|
+
headers: { "Content-Type": "application/json" },
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
const affiliate = await ctx.runQuery(component.affiliates.getByCode, {
|
|
889
|
+
code: code.toUpperCase(),
|
|
890
|
+
});
|
|
891
|
+
if (!affiliate) {
|
|
892
|
+
return new Response(JSON.stringify({ error: "Affiliate not found" }), {
|
|
893
|
+
status: 404,
|
|
894
|
+
headers: { "Content-Type": "application/json" },
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return new Response(JSON.stringify({
|
|
898
|
+
code: affiliate.code,
|
|
899
|
+
displayName: affiliate.displayName,
|
|
900
|
+
valid: affiliate.status === "approved",
|
|
901
|
+
}), {
|
|
902
|
+
status: 200,
|
|
903
|
+
headers: { "Content-Type": "application/json" },
|
|
904
|
+
});
|
|
905
|
+
}),
|
|
886
906
|
});
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
907
|
+
},
|
|
908
|
+
// =========================================================================
|
|
909
|
+
// Stripe Webhook Handler
|
|
910
|
+
// =========================================================================
|
|
911
|
+
/**
|
|
912
|
+
* Create a Stripe webhook handler that processes affiliate-related events.
|
|
913
|
+
* The component reference is already available from the closure.
|
|
914
|
+
*
|
|
915
|
+
* @param webhookConfig - Configuration with webhookSecret
|
|
916
|
+
* @returns An HTTP action handler for Stripe webhooks
|
|
917
|
+
*
|
|
918
|
+
* @example
|
|
919
|
+
* ```typescript
|
|
920
|
+
* const affiliates = createAffiliateApi(components.affiliates, { ... });
|
|
921
|
+
*
|
|
922
|
+
* http.route({
|
|
923
|
+
* path: "/webhooks/stripe",
|
|
924
|
+
* method: "POST",
|
|
925
|
+
* handler: affiliates.createStripeWebhookHandler({
|
|
926
|
+
* webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
927
|
+
* }),
|
|
928
|
+
* });
|
|
929
|
+
* ```
|
|
930
|
+
*/
|
|
931
|
+
createStripeWebhookHandler(webhookConfig) {
|
|
932
|
+
if (!webhookConfig.webhookSecret) {
|
|
933
|
+
throw new Error("webhookSecret is required for Stripe webhook handler. " +
|
|
934
|
+
"Set STRIPE_WEBHOOK_SECRET in your Convex environment variables.");
|
|
892
935
|
}
|
|
893
|
-
return
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
936
|
+
return httpActionGeneric(async (ctx, request) => {
|
|
937
|
+
const rawBody = await request.text();
|
|
938
|
+
const signature = request.headers.get("stripe-signature");
|
|
939
|
+
if (!signature) {
|
|
940
|
+
return new Response(JSON.stringify({ error: "Missing stripe-signature header" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
941
|
+
}
|
|
942
|
+
const isValid = await verifyStripeSignature(rawBody, signature, webhookConfig.webhookSecret);
|
|
943
|
+
if (!isValid) {
|
|
944
|
+
return new Response(JSON.stringify({ error: "Invalid signature" }), {
|
|
945
|
+
status: 401,
|
|
946
|
+
headers: { "Content-Type": "application/json" },
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
const event = JSON.parse(rawBody);
|
|
950
|
+
try {
|
|
951
|
+
switch (event.type) {
|
|
952
|
+
case "invoice.paid": {
|
|
953
|
+
const invoice = event.data.object;
|
|
954
|
+
await ctx.runMutation(component.commissions.createFromInvoice, {
|
|
955
|
+
stripeInvoiceId: invoice.id,
|
|
956
|
+
stripeCustomerId: invoice.customer,
|
|
957
|
+
stripeChargeId: invoice.charge,
|
|
958
|
+
stripeSubscriptionId: invoice.subscription,
|
|
959
|
+
stripeProductId: invoice.lines?.data?.[0]?.price?.product,
|
|
960
|
+
amountPaidCents: invoice.amount_paid,
|
|
961
|
+
currency: invoice.currency,
|
|
962
|
+
affiliateCode: invoice.metadata?.affiliate_code,
|
|
963
|
+
});
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
case "charge.refunded": {
|
|
967
|
+
const charge = event.data.object;
|
|
968
|
+
await ctx.runMutation(component.commissions.reverseByCharge, {
|
|
969
|
+
stripeChargeId: charge.id,
|
|
970
|
+
reason: charge.refunds?.data?.[0]?.reason ?? "Charge refunded",
|
|
971
|
+
});
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
case "checkout.session.completed": {
|
|
975
|
+
const session = event.data.object;
|
|
976
|
+
await ctx.runMutation(component.referrals.linkStripeCustomer, {
|
|
977
|
+
stripeCustomerId: session.customer,
|
|
978
|
+
userId: session.client_reference_id,
|
|
979
|
+
affiliateCode: session.metadata?.affiliate_code,
|
|
980
|
+
});
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return new Response(JSON.stringify({ received: true }), {
|
|
985
|
+
status: 200,
|
|
986
|
+
headers: { "Content-Type": "application/json" },
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
catch (error) {
|
|
990
|
+
console.error("Stripe webhook error:", error);
|
|
991
|
+
return new Response(JSON.stringify({
|
|
992
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
993
|
+
}), { status: 500, headers: { "Content-Type": "application/json" } });
|
|
994
|
+
}
|
|
900
995
|
});
|
|
901
|
-
}
|
|
902
|
-
}
|
|
996
|
+
},
|
|
997
|
+
};
|
|
903
998
|
}
|
|
904
999
|
// =============================================================================
|
|
905
|
-
// Stripe Webhook
|
|
1000
|
+
// Stripe Webhook Signature Verification
|
|
906
1001
|
// =============================================================================
|
|
907
1002
|
/**
|
|
908
1003
|
* Verify Stripe webhook signature without the Stripe SDK.
|
|
@@ -930,98 +1025,6 @@ async function verifyStripeSignature(payload, signature, secret) {
|
|
|
930
1025
|
// Constant-time comparison
|
|
931
1026
|
return sig === expectedSig;
|
|
932
1027
|
}
|
|
933
|
-
/**
|
|
934
|
-
* Create a Stripe webhook handler that processes affiliate-related events.
|
|
935
|
-
* Handles invoice.paid, charge.refunded, and checkout.session.completed events.
|
|
936
|
-
*
|
|
937
|
-
* @example
|
|
938
|
-
* ```typescript
|
|
939
|
-
* import { httpRouter } from "convex/server";
|
|
940
|
-
* import { createStripeWebhookHandler } from "convex-affiliates";
|
|
941
|
-
* import { components } from "./_generated/api";
|
|
942
|
-
*
|
|
943
|
-
* const http = httpRouter();
|
|
944
|
-
*
|
|
945
|
-
* http.route({
|
|
946
|
-
* path: "/webhooks/stripe",
|
|
947
|
-
* method: "POST",
|
|
948
|
-
* handler: createStripeWebhookHandler(components.affiliates, {
|
|
949
|
-
* webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
950
|
-
* }),
|
|
951
|
-
* });
|
|
952
|
-
*
|
|
953
|
-
* export default http;
|
|
954
|
-
* ```
|
|
955
|
-
*/
|
|
956
|
-
export function createStripeWebhookHandler(component, config) {
|
|
957
|
-
// Validate webhook secret at creation time for helpful error messages
|
|
958
|
-
if (!config.webhookSecret) {
|
|
959
|
-
throw new Error("webhookSecret is required for Stripe webhook handler. " +
|
|
960
|
-
"Set STRIPE_WEBHOOK_SECRET in your Convex environment variables.");
|
|
961
|
-
}
|
|
962
|
-
return httpActionGeneric(async (ctx, request) => {
|
|
963
|
-
// Get raw body for signature verification
|
|
964
|
-
const rawBody = await request.text();
|
|
965
|
-
// Verify signature (required)
|
|
966
|
-
const signature = request.headers.get("stripe-signature");
|
|
967
|
-
if (!signature) {
|
|
968
|
-
return new Response(JSON.stringify({ error: "Missing stripe-signature header" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
969
|
-
}
|
|
970
|
-
const isValid = await verifyStripeSignature(rawBody, signature, config.webhookSecret);
|
|
971
|
-
if (!isValid) {
|
|
972
|
-
return new Response(JSON.stringify({ error: "Invalid signature" }), {
|
|
973
|
-
status: 401,
|
|
974
|
-
headers: { "Content-Type": "application/json" },
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
const event = JSON.parse(rawBody);
|
|
978
|
-
try {
|
|
979
|
-
switch (event.type) {
|
|
980
|
-
case "invoice.paid": {
|
|
981
|
-
const invoice = event.data.object;
|
|
982
|
-
await ctx.runMutation(component.commissions.createFromInvoice, {
|
|
983
|
-
stripeInvoiceId: invoice.id,
|
|
984
|
-
stripeCustomerId: invoice.customer,
|
|
985
|
-
stripeChargeId: invoice.charge,
|
|
986
|
-
stripeSubscriptionId: invoice.subscription,
|
|
987
|
-
stripeProductId: invoice.lines?.data?.[0]?.price?.product,
|
|
988
|
-
amountPaidCents: invoice.amount_paid,
|
|
989
|
-
currency: invoice.currency,
|
|
990
|
-
affiliateCode: invoice.metadata?.affiliate_code,
|
|
991
|
-
});
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
case "charge.refunded": {
|
|
995
|
-
const charge = event.data.object;
|
|
996
|
-
await ctx.runMutation(component.commissions.reverseByCharge, {
|
|
997
|
-
stripeChargeId: charge.id,
|
|
998
|
-
reason: charge.refunds?.data?.[0]?.reason ?? "Charge refunded",
|
|
999
|
-
});
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
case "checkout.session.completed": {
|
|
1003
|
-
const session = event.data.object;
|
|
1004
|
-
await ctx.runMutation(component.referrals.linkStripeCustomer, {
|
|
1005
|
-
stripeCustomerId: session.customer,
|
|
1006
|
-
userId: session.client_reference_id,
|
|
1007
|
-
affiliateCode: session.metadata?.affiliate_code,
|
|
1008
|
-
});
|
|
1009
|
-
break;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return new Response(JSON.stringify({ received: true }), {
|
|
1013
|
-
status: 200,
|
|
1014
|
-
headers: { "Content-Type": "application/json" },
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
catch (error) {
|
|
1018
|
-
console.error("Stripe webhook error:", error);
|
|
1019
|
-
return new Response(JSON.stringify({
|
|
1020
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1021
|
-
}), { status: 500, headers: { "Content-Type": "application/json" } });
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
1028
|
/**
|
|
1026
1029
|
* Get Stripe event handlers for affiliate tracking.
|
|
1027
1030
|
* Optionally merge with your own handlers (affiliate runs first).
|