@better-auth/stripe 1.2.0-beta.18

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/dist/index.cjs ADDED
@@ -0,0 +1,779 @@
1
+ 'use strict';
2
+
3
+ const plugins = require('better-auth/plugins');
4
+ const zod = require('zod');
5
+ const api = require('better-auth/api');
6
+ const crypto = require('better-auth/crypto');
7
+
8
+ async function getPlans(options) {
9
+ return typeof options?.subscription?.plans === "function" ? await options.subscription?.plans() : options.subscription?.plans;
10
+ }
11
+ async function getPlanByPriceId(options, priceId) {
12
+ return await getPlans(options).then(
13
+ (res) => res?.find((plan) => plan.priceId === priceId)
14
+ );
15
+ }
16
+ async function getPlanByName(options, name) {
17
+ return await getPlans(options).then(
18
+ (res) => res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase())
19
+ );
20
+ }
21
+
22
+ async function onCheckoutSessionCompleted(ctx, options, event) {
23
+ const client = options.stripeClient;
24
+ const checkoutSession = event.data.object;
25
+ if (checkoutSession.mode === "setup" || !options.subscription?.enabled) {
26
+ return;
27
+ }
28
+ const subscription = await client.subscriptions.retrieve(
29
+ checkoutSession.subscription
30
+ );
31
+ const priceId = subscription.items.data[0]?.price.id;
32
+ const plan = await getPlanByPriceId(options, priceId);
33
+ if (plan) {
34
+ const referenceId = checkoutSession?.metadata?.referenceId;
35
+ const subscriptionId = checkoutSession?.metadata?.subscriptionId;
36
+ const seats = subscription.items.data[0].quantity;
37
+ if (referenceId && subscriptionId) {
38
+ const trial = subscription.trial_start && subscription.trial_end ? {
39
+ trialStart: new Date(subscription.trial_start * 1e3),
40
+ trialEnd: new Date(subscription.trial_end * 1e3)
41
+ } : {};
42
+ let dbSubscription = await ctx.context.adapter.update({
43
+ model: "subscription",
44
+ update: {
45
+ plan: plan.name.toLowerCase(),
46
+ status: subscription.status,
47
+ updatedAt: /* @__PURE__ */ new Date(),
48
+ periodStart: new Date(subscription.current_period_start * 1e3),
49
+ periodEnd: new Date(subscription.current_period_end * 1e3),
50
+ seats,
51
+ ...trial
52
+ },
53
+ where: [
54
+ {
55
+ field: "id",
56
+ value: subscriptionId
57
+ }
58
+ ]
59
+ });
60
+ if (!dbSubscription) {
61
+ dbSubscription = await ctx.context.adapter.findOne({
62
+ model: "subscription",
63
+ where: [
64
+ {
65
+ field: "id",
66
+ value: subscriptionId
67
+ }
68
+ ]
69
+ });
70
+ }
71
+ await options.subscription?.onSubscriptionComplete?.({
72
+ event,
73
+ subscription: dbSubscription,
74
+ stripeSubscription: subscription,
75
+ plan
76
+ });
77
+ return;
78
+ }
79
+ }
80
+ }
81
+ async function onSubscriptionUpdated(ctx, options, event) {
82
+ if (!options.subscription?.enabled) {
83
+ return;
84
+ }
85
+ const subscriptionUpdated = event.data.object;
86
+ const priceId = subscriptionUpdated.items.data[0].price.id;
87
+ const plan = await getPlanByPriceId(options, priceId);
88
+ if (plan) {
89
+ const stripeId = subscriptionUpdated.customer.toString();
90
+ const subscription = await ctx.context.adapter.findOne({
91
+ model: "subscription",
92
+ where: [
93
+ {
94
+ field: "stripeSubscriptionId",
95
+ value: stripeId
96
+ }
97
+ ]
98
+ });
99
+ if (!subscription) {
100
+ return;
101
+ }
102
+ const seats = subscriptionUpdated.items.data[0].quantity;
103
+ await ctx.context.adapter.update({
104
+ model: "subscription",
105
+ update: {
106
+ plan: plan.name.toLowerCase(),
107
+ limits: plan.limits,
108
+ updatedAt: /* @__PURE__ */ new Date(),
109
+ status: subscriptionUpdated.status,
110
+ periodStart: new Date(subscriptionUpdated.current_period_start * 1e3),
111
+ periodEnd: new Date(subscriptionUpdated.current_period_end * 1e3),
112
+ cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
113
+ seats
114
+ },
115
+ where: [
116
+ {
117
+ field: "stripeSubscriptionId",
118
+ value: subscriptionUpdated.id
119
+ }
120
+ ]
121
+ });
122
+ const subscriptionCanceled = subscriptionUpdated.status === "active" && subscriptionUpdated.cancel_at_period_end;
123
+ if (subscriptionCanceled) {
124
+ await options.subscription.onSubscriptionCancel?.({
125
+ subscription,
126
+ cancellationDetails: subscriptionUpdated.cancellation_details || void 0,
127
+ stripeSubscription: subscriptionUpdated,
128
+ event
129
+ });
130
+ }
131
+ await options.subscription.onSubscriptionUpdate?.({
132
+ event,
133
+ subscription
134
+ });
135
+ }
136
+ }
137
+ async function onSubscriptionDeleted(ctx, options, event) {
138
+ if (!options.subscription?.enabled) {
139
+ return;
140
+ }
141
+ const subscriptionDeleted = event.data.object;
142
+ const subscriptionId = subscriptionDeleted.metadata?.subscriptionId;
143
+ const stripeSubscription = await options.stripeClient.subscriptions.retrieve(
144
+ subscriptionId
145
+ );
146
+ if (stripeSubscription.status === "canceled") {
147
+ const subscription = await ctx.context.adapter.findOne({
148
+ model: "subscription",
149
+ where: [
150
+ {
151
+ field: "id",
152
+ value: subscriptionId
153
+ }
154
+ ]
155
+ });
156
+ if (subscription) {
157
+ await ctx.context.adapter.update({
158
+ model: "subscription",
159
+ where: [
160
+ {
161
+ field: "id",
162
+ value: subscription.id
163
+ }
164
+ ],
165
+ update: {
166
+ status: "canceled"
167
+ }
168
+ });
169
+ await options.subscription.onSubscriptionDeleted?.({
170
+ event,
171
+ stripeSubscription: subscriptionDeleted,
172
+ subscription
173
+ });
174
+ }
175
+ }
176
+ }
177
+
178
+ const getSchema = (options) => {
179
+ const subscriptions = {
180
+ subscription: {
181
+ fields: {
182
+ plan: {
183
+ type: "string",
184
+ required: true
185
+ },
186
+ referenceId: {
187
+ type: "string",
188
+ required: true
189
+ },
190
+ stripeCustomerId: {
191
+ type: "string",
192
+ required: false
193
+ },
194
+ stripeSubscriptionId: {
195
+ type: "string",
196
+ required: false
197
+ },
198
+ status: {
199
+ type: "string",
200
+ defaultValue: "incomplete"
201
+ },
202
+ periodStart: {
203
+ type: "date",
204
+ required: false
205
+ },
206
+ periodEnd: {
207
+ type: "date",
208
+ required: false
209
+ },
210
+ cancelAtPeriodEnd: {
211
+ type: "boolean",
212
+ required: false,
213
+ defaultValue: false
214
+ },
215
+ seats: {
216
+ type: "number",
217
+ required: false
218
+ }
219
+ }
220
+ }
221
+ };
222
+ const user = {
223
+ user: {
224
+ fields: {
225
+ stripeCustomerId: {
226
+ type: "string",
227
+ required: false
228
+ }
229
+ }
230
+ }
231
+ };
232
+ return {
233
+ ...options.subscription?.enabled ? subscriptions : {},
234
+ ...user
235
+ };
236
+ };
237
+
238
+ const STRIPE_ERROR_CODES = {
239
+ SUBSCRIPTION_NOT_FOUND: "Subscription not found",
240
+ SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
241
+ ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
242
+ UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
243
+ EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan"
244
+ };
245
+ const getUrl = (ctx, url) => {
246
+ if (url.startsWith("http")) {
247
+ return url;
248
+ }
249
+ return `${ctx.context.options.baseURL}${url.startsWith("/") ? url : `/${url}`}`;
250
+ };
251
+ const stripe = (options) => {
252
+ const client = options.stripeClient;
253
+ const referenceMiddleware = (action) => plugins.createAuthMiddleware(async (ctx) => {
254
+ const session = ctx.context.session;
255
+ if (!session) {
256
+ throw new api.APIError("UNAUTHORIZED");
257
+ }
258
+ const referenceId = ctx.body?.referenceId || ctx.query?.referenceId || session.user.id;
259
+ const isAuthorized = ctx.body?.referenceId ? await options.subscription?.authorizeReference?.({
260
+ user: session.user,
261
+ session: session.session,
262
+ referenceId,
263
+ action
264
+ }) : true;
265
+ if (!isAuthorized) {
266
+ throw new api.APIError("UNAUTHORIZED", {
267
+ message: "Unauthorized"
268
+ });
269
+ }
270
+ });
271
+ const subscriptionEndpoints = {
272
+ upgradeSubscription: plugins.createAuthEndpoint(
273
+ "/subscription/upgrade",
274
+ {
275
+ method: "POST",
276
+ body: zod.z.object({
277
+ plan: zod.z.string(),
278
+ referenceId: zod.z.string().optional(),
279
+ metadata: zod.z.record(zod.z.string(), zod.z.any()).optional(),
280
+ seats: zod.z.number({
281
+ description: "Number of seats to upgrade to (if applicable)"
282
+ }).optional(),
283
+ uiMode: zod.z.enum(["embedded", "hosted"]).default("hosted"),
284
+ successUrl: zod.z.string({
285
+ description: "callback url to redirect back after successful subscription"
286
+ }).default("/"),
287
+ cancelUrl: zod.z.string({
288
+ description: "callback url to redirect back after successful subscription"
289
+ }).default("/"),
290
+ returnUrl: zod.z.string().optional(),
291
+ withoutTrial: zod.z.boolean().optional(),
292
+ disableRedirect: zod.z.boolean().default(false)
293
+ }),
294
+ use: [
295
+ api.sessionMiddleware,
296
+ api.originCheck((c) => {
297
+ return [c.body.successURL, c.body.cancelURL];
298
+ }),
299
+ referenceMiddleware("upgrade-subscription")
300
+ ]
301
+ },
302
+ async (ctx) => {
303
+ const { user, session } = ctx.context.session;
304
+ if (!user.emailVerified && options.subscription?.requireEmailVerification) {
305
+ throw new api.APIError("BAD_REQUEST", {
306
+ message: STRIPE_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED
307
+ });
308
+ }
309
+ const referenceId = ctx.body.referenceId || user.id;
310
+ const plan = await getPlanByName(options, ctx.body.plan);
311
+ if (!plan) {
312
+ throw new api.APIError("BAD_REQUEST", {
313
+ message: STRIPE_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND
314
+ });
315
+ }
316
+ let customerId = user.stripeCustomerId;
317
+ if (!customerId) {
318
+ try {
319
+ const stripeCustomer = await client.customers.create(
320
+ {
321
+ email: user.email,
322
+ name: user.name,
323
+ metadata: {
324
+ ...ctx.body.metadata,
325
+ userId: user.id
326
+ }
327
+ },
328
+ {
329
+ idempotencyKey: crypto.generateRandomString(32, "a-z", "0-9")
330
+ }
331
+ );
332
+ await ctx.context.adapter.update({
333
+ model: "user",
334
+ update: {
335
+ stripeCustomerId: stripeCustomer.id
336
+ },
337
+ where: [
338
+ {
339
+ field: "id",
340
+ value: user.id
341
+ }
342
+ ]
343
+ });
344
+ customerId = stripeCustomer.id;
345
+ } catch (e) {
346
+ ctx.context.logger.error(e);
347
+ throw new api.APIError("BAD_REQUEST", {
348
+ message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER
349
+ });
350
+ }
351
+ }
352
+ const activeSubscription = customerId ? await client.subscriptions.list({
353
+ customer: customerId,
354
+ status: "active"
355
+ }).then((res) => res.data[0]).catch((e) => null) : null;
356
+ const subscriptions = await ctx.context.adapter.findMany({
357
+ model: "subscription",
358
+ where: [
359
+ {
360
+ field: "referenceId",
361
+ value: ctx.body.referenceId || user.id
362
+ }
363
+ ]
364
+ });
365
+ const existingSubscription = subscriptions.find(
366
+ (sub) => sub.status === "active" || sub.status === "trialing"
367
+ );
368
+ if (activeSubscription && customerId) {
369
+ const { url } = await client.billingPortal.sessions.create({
370
+ customer: customerId,
371
+ return_url: getUrl(ctx, ctx.body.returnUrl || "/"),
372
+ flow_data: {
373
+ type: "subscription_update_confirm",
374
+ subscription_update_confirm: {
375
+ subscription: activeSubscription.id,
376
+ items: [
377
+ {
378
+ id: activeSubscription.items.data[0]?.id,
379
+ quantity: 1,
380
+ price: plan.priceId
381
+ }
382
+ ]
383
+ }
384
+ }
385
+ }).catch(async (e) => {
386
+ if (e.message.includes("no changes")) {
387
+ const plan2 = await getPlanByPriceId(
388
+ options,
389
+ activeSubscription.items.data[0]?.plan.id
390
+ );
391
+ await ctx.context.adapter.update({
392
+ model: "subscription",
393
+ update: {
394
+ status: activeSubscription.status,
395
+ seats: activeSubscription.items.data[0]?.quantity,
396
+ plan: plan2?.name.toLowerCase()
397
+ },
398
+ where: [
399
+ {
400
+ field: "referenceId",
401
+ value: referenceId
402
+ }
403
+ ]
404
+ });
405
+ throw new api.APIError("BAD_REQUEST", {
406
+ message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
407
+ });
408
+ }
409
+ throw ctx.error("BAD_REQUEST", {
410
+ message: e.message,
411
+ code: e.code
412
+ });
413
+ });
414
+ return ctx.json({
415
+ url,
416
+ redirect: true
417
+ });
418
+ }
419
+ if (existingSubscription && existingSubscription.status === "active" && existingSubscription.plan === ctx.body.plan) {
420
+ throw new api.APIError("BAD_REQUEST", {
421
+ message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
422
+ });
423
+ }
424
+ let subscription = existingSubscription;
425
+ if (!subscription) {
426
+ const newSubscription = await ctx.context.adapter.create({
427
+ model: "subscription",
428
+ data: {
429
+ plan: plan.name.toLowerCase(),
430
+ stripeCustomerId: customerId,
431
+ status: "incomplete",
432
+ referenceId,
433
+ seats: ctx.body.seats || 1
434
+ }
435
+ });
436
+ subscription = newSubscription;
437
+ }
438
+ if (!subscription) {
439
+ ctx.context.logger.error("Subscription ID not found");
440
+ throw new api.APIError("INTERNAL_SERVER_ERROR");
441
+ }
442
+ const params = await options.subscription?.getCheckoutSessionParams?.(
443
+ {
444
+ user,
445
+ session,
446
+ plan,
447
+ subscription
448
+ },
449
+ ctx.request
450
+ );
451
+ const checkoutSession = await client.checkout.sessions.create({
452
+ ...customerId ? {
453
+ customer: customerId,
454
+ customer_update: {
455
+ name: "auto",
456
+ address: "auto"
457
+ }
458
+ } : {
459
+ customer_email: session.user.email
460
+ },
461
+ success_url: getUrl(
462
+ ctx,
463
+ `${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(
464
+ ctx.body.successUrl
465
+ )}&reference=${encodeURIComponent(referenceId)}`
466
+ ),
467
+ cancel_url: getUrl(ctx, ctx.body.cancelUrl),
468
+ line_items: [
469
+ {
470
+ price: plan.priceId,
471
+ quantity: ctx.body.seats || 1
472
+ }
473
+ ],
474
+ mode: "subscription",
475
+ client_reference_id: referenceId,
476
+ ...params,
477
+ metadata: {
478
+ userId: user.id,
479
+ subscriptionId: subscription.id,
480
+ referenceId,
481
+ ...params?.params?.metadata
482
+ }
483
+ }).catch(async (e) => {
484
+ throw ctx.error("BAD_REQUEST", {
485
+ message: e.message,
486
+ code: e.code
487
+ });
488
+ });
489
+ return ctx.json({
490
+ ...checkoutSession,
491
+ redirect: !ctx.body.disableRedirect
492
+ });
493
+ }
494
+ ),
495
+ cancelSubscription: plugins.createAuthEndpoint(
496
+ "/subscription/cancel",
497
+ {
498
+ method: "POST",
499
+ body: zod.z.object({
500
+ referenceId: zod.z.string().optional(),
501
+ returnUrl: zod.z.string()
502
+ }),
503
+ use: [
504
+ api.sessionMiddleware,
505
+ api.originCheck((ctx) => ctx.body.returnUrl),
506
+ referenceMiddleware("cancel-subscription")
507
+ ]
508
+ },
509
+ async (ctx) => {
510
+ const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
511
+ const subscription = await ctx.context.adapter.findOne({
512
+ model: "subscription",
513
+ where: [
514
+ {
515
+ field: "referenceId",
516
+ value: referenceId
517
+ }
518
+ ]
519
+ });
520
+ if (!subscription || !subscription.stripeCustomerId) {
521
+ throw ctx.error("BAD_REQUEST", {
522
+ message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
523
+ });
524
+ }
525
+ const activeSubscription = await client.subscriptions.list({
526
+ customer: subscription.stripeCustomerId,
527
+ status: "active"
528
+ }).then((res) => res.data[0]);
529
+ if (!activeSubscription) {
530
+ throw ctx.error("BAD_REQUEST", {
531
+ message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
532
+ });
533
+ }
534
+ const { url } = await client.billingPortal.sessions.create({
535
+ customer: subscription.stripeCustomerId,
536
+ return_url: getUrl(ctx, ctx.body?.returnUrl || "/"),
537
+ flow_data: {
538
+ type: "subscription_cancel",
539
+ subscription_cancel: {
540
+ subscription: activeSubscription.id
541
+ }
542
+ }
543
+ });
544
+ return {
545
+ url,
546
+ redirect: true
547
+ };
548
+ }
549
+ ),
550
+ listActiveSubscriptions: plugins.createAuthEndpoint(
551
+ "/subscription/list",
552
+ {
553
+ method: "GET",
554
+ query: zod.z.optional(
555
+ zod.z.object({
556
+ referenceId: zod.z.string().optional()
557
+ })
558
+ ),
559
+ use: [api.sessionMiddleware, referenceMiddleware("list-subscription")]
560
+ },
561
+ async (ctx) => {
562
+ const subscriptions = await ctx.context.adapter.findMany({
563
+ model: "subscription",
564
+ where: [
565
+ {
566
+ field: "referenceId",
567
+ value: ctx.query?.referenceId || ctx.context.session.user.id
568
+ }
569
+ ]
570
+ });
571
+ if (!subscriptions.length) {
572
+ return [];
573
+ }
574
+ const plans = await getPlans(options);
575
+ if (!plans) {
576
+ return [];
577
+ }
578
+ const subs = subscriptions.map((sub) => {
579
+ const plan = plans.find(
580
+ (p) => p.name.toLowerCase() === sub.plan.toLowerCase()
581
+ );
582
+ return {
583
+ ...sub,
584
+ limits: plan?.limits
585
+ };
586
+ }).filter((sub) => {
587
+ return sub.status === "active" || sub.status === "trialing";
588
+ });
589
+ return ctx.json(subs);
590
+ }
591
+ ),
592
+ subscriptionSuccess: plugins.createAuthEndpoint(
593
+ "/subscription/success",
594
+ {
595
+ method: "GET",
596
+ query: zod.z.record(zod.z.string(), zod.z.any()).optional()
597
+ },
598
+ async (ctx) => {
599
+ if (!ctx.query || !ctx.query.callbackURL || !ctx.query.reference) {
600
+ throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
601
+ }
602
+ const session = await api.getSessionFromCtx(
603
+ ctx
604
+ );
605
+ if (!session) {
606
+ throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
607
+ }
608
+ const { user } = session;
609
+ const { callbackURL, reference } = ctx.query;
610
+ const subscriptions = await ctx.context.adapter.findMany({
611
+ model: "subscription",
612
+ where: [
613
+ {
614
+ field: "referenceId",
615
+ value: reference
616
+ }
617
+ ]
618
+ });
619
+ const activeSubscription = subscriptions.find(
620
+ (sub) => sub.status === "active" || sub.status === "trialing"
621
+ );
622
+ if (activeSubscription) {
623
+ return ctx.redirect(getUrl(ctx, callbackURL));
624
+ }
625
+ if (user?.stripeCustomerId) {
626
+ try {
627
+ const subscription = await ctx.context.adapter.findOne({
628
+ model: "subscription",
629
+ where: [
630
+ {
631
+ field: "referenceId",
632
+ value: reference
633
+ }
634
+ ]
635
+ });
636
+ if (!subscription || subscription.status === "active") {
637
+ throw ctx.redirect(getUrl(ctx, callbackURL));
638
+ }
639
+ const stripeSubscription = await client.subscriptions.list({
640
+ customer: user.stripeCustomerId,
641
+ status: "active"
642
+ }).then((res) => res.data[0]);
643
+ if (stripeSubscription) {
644
+ const plan = await getPlanByPriceId(
645
+ options,
646
+ stripeSubscription.items.data[0]?.plan.id
647
+ );
648
+ if (plan && subscriptions.length > 0) {
649
+ await ctx.context.adapter.update({
650
+ model: "subscription",
651
+ update: {
652
+ status: stripeSubscription.status,
653
+ seats: stripeSubscription.items.data[0]?.quantity || 1,
654
+ plan: plan.name.toLowerCase()
655
+ },
656
+ where: [
657
+ {
658
+ field: "referenceId",
659
+ value: reference
660
+ }
661
+ ]
662
+ });
663
+ }
664
+ }
665
+ } catch (error) {
666
+ ctx.context.logger.error(
667
+ "Error fetching subscription from Stripe",
668
+ error
669
+ );
670
+ }
671
+ }
672
+ throw ctx.redirect(getUrl(ctx, callbackURL));
673
+ }
674
+ )
675
+ };
676
+ return {
677
+ id: "stripe",
678
+ endpoints: {
679
+ stripeWebhook: plugins.createAuthEndpoint(
680
+ "/stripe/webhook",
681
+ {
682
+ method: "POST",
683
+ metadata: {
684
+ isAction: false
685
+ },
686
+ cloneRequest: true
687
+ },
688
+ async (ctx) => {
689
+ if (!ctx.request?.body) {
690
+ throw new api.APIError("INTERNAL_SERVER_ERROR");
691
+ }
692
+ const buf = await ctx.request.text();
693
+ const sig = ctx.request.headers.get("stripe-signature");
694
+ const webhookSecret = options.stripeWebhookSecret;
695
+ let event;
696
+ try {
697
+ if (!sig || !webhookSecret) {
698
+ throw new api.APIError("BAD_REQUEST", {
699
+ message: "Stripe webhook secret not found"
700
+ });
701
+ }
702
+ event = client.webhooks.constructEvent(buf, sig, webhookSecret);
703
+ } catch (err) {
704
+ ctx.context.logger.error(`${err.message}`);
705
+ throw new api.APIError("BAD_REQUEST", {
706
+ message: `Webhook Error: ${err.message}`
707
+ });
708
+ }
709
+ try {
710
+ switch (event.type) {
711
+ case "checkout.session.completed":
712
+ await onCheckoutSessionCompleted(ctx, options, event);
713
+ await options.onEvent?.(event);
714
+ break;
715
+ case "customer.subscription.updated":
716
+ await onSubscriptionUpdated(ctx, options, event);
717
+ await options.onEvent?.(event);
718
+ break;
719
+ case "customer.subscription.deleted":
720
+ await onSubscriptionDeleted(ctx, options, event);
721
+ await options.onEvent?.(event);
722
+ break;
723
+ default:
724
+ await options.onEvent?.(event);
725
+ break;
726
+ }
727
+ } catch (e) {
728
+ ctx.context.logger.error(
729
+ `Stripe webhook failed. Error: ${e.message}`
730
+ );
731
+ throw new api.APIError("BAD_REQUEST", {
732
+ message: "Webhook error: See server logs for more information."
733
+ });
734
+ }
735
+ return ctx.json({ success: true });
736
+ }
737
+ ),
738
+ ...options.subscription?.enabled ? subscriptionEndpoints : {}
739
+ },
740
+ init(ctx) {
741
+ return {
742
+ options: {
743
+ databaseHooks: {
744
+ user: {
745
+ create: {
746
+ async after(user, ctx2) {
747
+ if (ctx2 && options.createCustomerOnSignUp) {
748
+ const stripeCustomer = await client.customers.create({
749
+ email: user.email,
750
+ name: user.name,
751
+ metadata: {
752
+ userId: user.id
753
+ }
754
+ });
755
+ await ctx2.context.adapter.update({
756
+ model: "user",
757
+ update: {
758
+ stripeCustomerId: stripeCustomer.id
759
+ },
760
+ where: [
761
+ {
762
+ field: "id",
763
+ value: user.id
764
+ }
765
+ ]
766
+ });
767
+ }
768
+ }
769
+ }
770
+ }
771
+ }
772
+ }
773
+ };
774
+ },
775
+ schema: getSchema(options)
776
+ };
777
+ };
778
+
779
+ exports.stripe = stripe;