@better-auth/stripe 1.2.2-beta.3 → 1.2.2-beta.5
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/.turbo/turbo-build.log +3 -3
- package/dist/index.cjs +103 -93
- package/dist/index.d.cts +4 -10
- package/dist/index.d.mts +4 -10
- package/dist/index.d.ts +4 -10
- package/dist/index.mjs +103 -93
- package/package.json +2 -2
- package/src/hooks.ts +26 -26
- package/src/index.ts +99 -77
- package/src/types.ts +0 -6
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/stripe@1.2.2-beta.
|
|
2
|
+
> @better-auth/stripe@1.2.2-beta.5 build /home/runner/work/better-auth/better-auth/packages/stripe
|
|
3
3
|
> unbuild
|
|
4
4
|
|
|
5
5
|
[info] Automatically detected entries: src/index, src/client [esm] [cjs] [dts]
|
|
6
6
|
[info] Building stripe
|
|
7
7
|
[success] Build succeeded for stripe
|
|
8
|
-
[log] dist/index.cjs (total size: 30.
|
|
8
|
+
[log] dist/index.cjs (total size: 30.7 kB, chunk size: 30.7 kB, exports: stripe)
|
|
9
9
|
|
|
10
10
|
[log] dist/client.cjs (total size: 160 B, chunk size: 160 B, exports: stripeClient)
|
|
11
11
|
|
|
12
|
-
[log] dist/index.mjs (total size: 30.
|
|
12
|
+
[log] dist/index.mjs (total size: 30.5 kB, chunk size: 30.5 kB, exports: stripe)
|
|
13
13
|
|
|
14
14
|
[log] dist/client.mjs (total size: 133 B, chunk size: 133 B, exports: stripeClient)
|
|
15
15
|
|
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
|
|
|
51
51
|
periodEnd: new Date(subscription.current_period_end * 1e3),
|
|
52
52
|
stripeSubscriptionId: checkoutSession.subscription,
|
|
53
53
|
seats,
|
|
54
|
+
stripeCustomerId: subscription.customer.toString(),
|
|
54
55
|
...trial
|
|
55
56
|
},
|
|
56
57
|
where: [
|
|
@@ -127,6 +128,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
127
128
|
periodStart: new Date(subscriptionUpdated.current_period_start * 1e3),
|
|
128
129
|
periodEnd: new Date(subscriptionUpdated.current_period_end * 1e3),
|
|
129
130
|
cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
|
|
131
|
+
stripeCustomerId: subscriptionUpdated.customer.toString(),
|
|
130
132
|
seats,
|
|
131
133
|
stripeSubscriptionId: subscriptionUpdated.id
|
|
132
134
|
},
|
|
@@ -175,40 +177,38 @@ async function onSubscriptionDeleted(ctx, options, event) {
|
|
|
175
177
|
try {
|
|
176
178
|
const subscriptionDeleted = event.data.object;
|
|
177
179
|
const subscriptionId = subscriptionDeleted.id;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
const subscription = await ctx.context.adapter.findOne({
|
|
181
|
+
model: "subscription",
|
|
182
|
+
where: [
|
|
183
|
+
{
|
|
184
|
+
field: "stripeSubscriptionId",
|
|
185
|
+
value: subscriptionId
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
if (subscription) {
|
|
190
|
+
await ctx.context.adapter.update({
|
|
180
191
|
model: "subscription",
|
|
181
192
|
where: [
|
|
182
193
|
{
|
|
183
194
|
field: "stripeSubscriptionId",
|
|
184
195
|
value: subscriptionId
|
|
185
196
|
}
|
|
186
|
-
]
|
|
197
|
+
],
|
|
198
|
+
update: {
|
|
199
|
+
status: "canceled",
|
|
200
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
201
|
+
}
|
|
187
202
|
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
update: {
|
|
198
|
-
status: "canceled",
|
|
199
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
await options.subscription.onSubscriptionDeleted?.({
|
|
203
|
-
event,
|
|
204
|
-
stripeSubscription: subscriptionDeleted,
|
|
205
|
-
subscription
|
|
206
|
-
});
|
|
207
|
-
} else {
|
|
208
|
-
betterAuth.logger.warn(
|
|
209
|
-
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
203
|
+
await options.subscription.onSubscriptionDeleted?.({
|
|
204
|
+
event,
|
|
205
|
+
stripeSubscription: subscriptionDeleted,
|
|
206
|
+
subscription
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
betterAuth.logger.warn(
|
|
210
|
+
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`
|
|
211
|
+
);
|
|
212
212
|
}
|
|
213
213
|
} catch (error) {
|
|
214
214
|
betterAuth.logger.error(`Stripe webhook failed. Error: ${error}`);
|
|
@@ -314,7 +314,12 @@ const stripe = (options) => {
|
|
|
314
314
|
{
|
|
315
315
|
method: "POST",
|
|
316
316
|
body: zod.z.object({
|
|
317
|
-
plan: zod.z.string(
|
|
317
|
+
plan: zod.z.string({
|
|
318
|
+
description: "The name of the plan to upgrade to"
|
|
319
|
+
}),
|
|
320
|
+
annual: zod.z.boolean({
|
|
321
|
+
description: "Whether to upgrade to an annual plan"
|
|
322
|
+
}).optional(),
|
|
318
323
|
referenceId: zod.z.string().optional(),
|
|
319
324
|
metadata: zod.z.record(zod.z.string(), zod.z.any()).optional(),
|
|
320
325
|
seats: zod.z.number({
|
|
@@ -328,7 +333,6 @@ const stripe = (options) => {
|
|
|
328
333
|
description: "callback url to redirect back after successful subscription"
|
|
329
334
|
}).default("/"),
|
|
330
335
|
returnUrl: zod.z.string().optional(),
|
|
331
|
-
withoutTrial: zod.z.boolean().optional(),
|
|
332
336
|
disableRedirect: zod.z.boolean().default(false)
|
|
333
337
|
}),
|
|
334
338
|
use: [
|
|
@@ -405,6 +409,11 @@ const stripe = (options) => {
|
|
|
405
409
|
const existingSubscription = subscriptions.find(
|
|
406
410
|
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
407
411
|
);
|
|
412
|
+
if (existingSubscription && existingSubscription.status === "active" && existingSubscription.plan === ctx.body.plan) {
|
|
413
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
414
|
+
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
|
|
415
|
+
});
|
|
416
|
+
}
|
|
408
417
|
if (activeSubscription && customerId) {
|
|
409
418
|
const { url } = await client.billingPortal.sessions.create({
|
|
410
419
|
customer: customerId,
|
|
@@ -417,35 +426,12 @@ const stripe = (options) => {
|
|
|
417
426
|
{
|
|
418
427
|
id: activeSubscription.items.data[0]?.id,
|
|
419
428
|
quantity: 1,
|
|
420
|
-
price: plan.priceId
|
|
429
|
+
price: ctx.body.annual ? plan.annualDiscountPriceId : plan.priceId
|
|
421
430
|
}
|
|
422
431
|
]
|
|
423
432
|
}
|
|
424
433
|
}
|
|
425
434
|
}).catch(async (e) => {
|
|
426
|
-
if (e.message.includes("no changes")) {
|
|
427
|
-
const plan2 = await getPlanByPriceId(
|
|
428
|
-
options,
|
|
429
|
-
activeSubscription.items.data[0]?.plan.id
|
|
430
|
-
);
|
|
431
|
-
await ctx.context.adapter.update({
|
|
432
|
-
model: "subscription",
|
|
433
|
-
update: {
|
|
434
|
-
status: activeSubscription.status,
|
|
435
|
-
seats: activeSubscription.items.data[0]?.quantity,
|
|
436
|
-
plan: plan2?.name.toLowerCase()
|
|
437
|
-
},
|
|
438
|
-
where: [
|
|
439
|
-
{
|
|
440
|
-
field: "referenceId",
|
|
441
|
-
value: referenceId
|
|
442
|
-
}
|
|
443
|
-
]
|
|
444
|
-
});
|
|
445
|
-
throw new api.APIError("BAD_REQUEST", {
|
|
446
|
-
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
435
|
throw ctx.error("BAD_REQUEST", {
|
|
450
436
|
message: e.message,
|
|
451
437
|
code: e.code
|
|
@@ -456,11 +442,6 @@ const stripe = (options) => {
|
|
|
456
442
|
redirect: true
|
|
457
443
|
});
|
|
458
444
|
}
|
|
459
|
-
if (existingSubscription && existingSubscription.status === "active" && existingSubscription.plan === ctx.body.plan) {
|
|
460
|
-
throw new api.APIError("BAD_REQUEST", {
|
|
461
|
-
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
445
|
let subscription = existingSubscription;
|
|
465
446
|
if (!subscription) {
|
|
466
447
|
const newSubscription = await ctx.context.adapter.create({
|
|
@@ -488,39 +469,48 @@ const stripe = (options) => {
|
|
|
488
469
|
},
|
|
489
470
|
ctx.request
|
|
490
471
|
);
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
472
|
+
const freeTrail = plan.freeTrial ? {
|
|
473
|
+
trial_period_days: plan.freeTrial.days
|
|
474
|
+
} : void 0;
|
|
475
|
+
const checkoutSession = await client.checkout.sessions.create(
|
|
476
|
+
{
|
|
477
|
+
...customerId ? {
|
|
478
|
+
customer: customerId,
|
|
479
|
+
customer_update: {
|
|
480
|
+
name: "auto",
|
|
481
|
+
address: "auto"
|
|
482
|
+
}
|
|
483
|
+
} : {
|
|
484
|
+
customer_email: session.user.email
|
|
485
|
+
},
|
|
486
|
+
success_url: getUrl(
|
|
487
|
+
ctx,
|
|
488
|
+
`${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(
|
|
489
|
+
ctx.body.successUrl
|
|
490
|
+
)}&reference=${encodeURIComponent(referenceId)}`
|
|
491
|
+
),
|
|
492
|
+
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
493
|
+
line_items: [
|
|
494
|
+
{
|
|
495
|
+
price: ctx.body.annual ? plan.annualDiscountPriceId : plan.priceId,
|
|
496
|
+
quantity: ctx.body.seats || 1
|
|
497
|
+
}
|
|
498
|
+
],
|
|
499
|
+
subscription_data: {
|
|
500
|
+
...freeTrail
|
|
501
|
+
},
|
|
502
|
+
mode: "subscription",
|
|
503
|
+
client_reference_id: referenceId,
|
|
504
|
+
...params?.params,
|
|
505
|
+
metadata: {
|
|
506
|
+
userId: user.id,
|
|
507
|
+
subscriptionId: subscription.id,
|
|
508
|
+
referenceId,
|
|
509
|
+
...params?.params?.metadata
|
|
497
510
|
}
|
|
498
|
-
} : {
|
|
499
|
-
customer_email: session.user.email
|
|
500
511
|
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
`${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(
|
|
504
|
-
ctx.body.successUrl
|
|
505
|
-
)}&reference=${encodeURIComponent(referenceId)}`
|
|
506
|
-
),
|
|
507
|
-
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
508
|
-
line_items: [
|
|
509
|
-
{
|
|
510
|
-
price: plan.priceId,
|
|
511
|
-
quantity: ctx.body.seats || 1
|
|
512
|
-
}
|
|
513
|
-
],
|
|
514
|
-
mode: "subscription",
|
|
515
|
-
client_reference_id: referenceId,
|
|
516
|
-
...params,
|
|
517
|
-
metadata: {
|
|
518
|
-
userId: user.id,
|
|
519
|
-
subscriptionId: subscription.id,
|
|
520
|
-
referenceId,
|
|
521
|
-
...params?.params?.metadata
|
|
522
|
-
}
|
|
523
|
-
}).catch(async (e) => {
|
|
512
|
+
params?.options
|
|
513
|
+
).catch(async (e) => {
|
|
524
514
|
throw ctx.error("BAD_REQUEST", {
|
|
525
515
|
message: e.message,
|
|
526
516
|
code: e.code
|
|
@@ -632,10 +622,30 @@ const stripe = (options) => {
|
|
|
632
622
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
633
623
|
});
|
|
634
624
|
}
|
|
635
|
-
const
|
|
636
|
-
customer: subscription.stripeCustomerId
|
|
637
|
-
|
|
638
|
-
|
|
625
|
+
const activeSubscriptions = await client.subscriptions.list({
|
|
626
|
+
customer: subscription.stripeCustomerId
|
|
627
|
+
}).then(
|
|
628
|
+
(res) => res.data.filter(
|
|
629
|
+
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
630
|
+
)
|
|
631
|
+
);
|
|
632
|
+
if (!activeSubscriptions.length) {
|
|
633
|
+
await ctx.context.adapter.deleteMany({
|
|
634
|
+
model: "subscription",
|
|
635
|
+
where: [
|
|
636
|
+
{
|
|
637
|
+
field: "referenceId",
|
|
638
|
+
value: referenceId
|
|
639
|
+
}
|
|
640
|
+
]
|
|
641
|
+
});
|
|
642
|
+
throw ctx.error("BAD_REQUEST", {
|
|
643
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const activeSubscription = activeSubscriptions.find(
|
|
647
|
+
(sub) => sub.id === subscription.stripeSubscriptionId
|
|
648
|
+
);
|
|
639
649
|
if (!activeSubscription) {
|
|
640
650
|
throw ctx.error("BAD_REQUEST", {
|
|
641
651
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
package/dist/index.d.cts
CHANGED
|
@@ -47,12 +47,6 @@ type Plan = {
|
|
|
47
47
|
* Number of days
|
|
48
48
|
*/
|
|
49
49
|
days: number;
|
|
50
|
-
/**
|
|
51
|
-
* Only available for new users or users without existing subscription
|
|
52
|
-
*
|
|
53
|
-
* @default true
|
|
54
|
-
*/
|
|
55
|
-
forNewUsersOnly?: boolean;
|
|
56
50
|
/**
|
|
57
51
|
* A function that will be called when the trial
|
|
58
52
|
* starts.
|
|
@@ -335,13 +329,13 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
335
329
|
body: {
|
|
336
330
|
plan: string;
|
|
337
331
|
metadata?: Record<string, any> | undefined;
|
|
332
|
+
annual?: boolean | undefined;
|
|
338
333
|
referenceId?: string | undefined;
|
|
339
334
|
seats?: number | undefined;
|
|
340
335
|
uiMode?: "embedded" | "hosted" | undefined;
|
|
341
336
|
successUrl?: string | undefined;
|
|
342
337
|
cancelUrl?: string | undefined;
|
|
343
338
|
returnUrl?: string | undefined;
|
|
344
|
-
withoutTrial?: boolean | undefined;
|
|
345
339
|
disableRedirect?: boolean | undefined;
|
|
346
340
|
};
|
|
347
341
|
method?: "POST" | undefined;
|
|
@@ -511,6 +505,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
511
505
|
method: "POST";
|
|
512
506
|
body: z.ZodObject<{
|
|
513
507
|
plan: z.ZodString;
|
|
508
|
+
annual: z.ZodOptional<z.ZodBoolean>;
|
|
514
509
|
referenceId: z.ZodOptional<z.ZodString>;
|
|
515
510
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
516
511
|
seats: z.ZodOptional<z.ZodNumber>;
|
|
@@ -518,7 +513,6 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
518
513
|
successUrl: z.ZodDefault<z.ZodString>;
|
|
519
514
|
cancelUrl: z.ZodDefault<z.ZodString>;
|
|
520
515
|
returnUrl: z.ZodOptional<z.ZodString>;
|
|
521
|
-
withoutTrial: z.ZodOptional<z.ZodBoolean>;
|
|
522
516
|
disableRedirect: z.ZodDefault<z.ZodBoolean>;
|
|
523
517
|
}, "strip", z.ZodTypeAny, {
|
|
524
518
|
plan: string;
|
|
@@ -527,20 +521,20 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
527
521
|
cancelUrl: string;
|
|
528
522
|
disableRedirect: boolean;
|
|
529
523
|
metadata?: Record<string, any> | undefined;
|
|
524
|
+
annual?: boolean | undefined;
|
|
530
525
|
referenceId?: string | undefined;
|
|
531
526
|
seats?: number | undefined;
|
|
532
527
|
returnUrl?: string | undefined;
|
|
533
|
-
withoutTrial?: boolean | undefined;
|
|
534
528
|
}, {
|
|
535
529
|
plan: string;
|
|
536
530
|
metadata?: Record<string, any> | undefined;
|
|
531
|
+
annual?: boolean | undefined;
|
|
537
532
|
referenceId?: string | undefined;
|
|
538
533
|
seats?: number | undefined;
|
|
539
534
|
uiMode?: "embedded" | "hosted" | undefined;
|
|
540
535
|
successUrl?: string | undefined;
|
|
541
536
|
cancelUrl?: string | undefined;
|
|
542
537
|
returnUrl?: string | undefined;
|
|
543
|
-
withoutTrial?: boolean | undefined;
|
|
544
538
|
disableRedirect?: boolean | undefined;
|
|
545
539
|
}>;
|
|
546
540
|
use: (((inputContext: {
|
package/dist/index.d.mts
CHANGED
|
@@ -47,12 +47,6 @@ type Plan = {
|
|
|
47
47
|
* Number of days
|
|
48
48
|
*/
|
|
49
49
|
days: number;
|
|
50
|
-
/**
|
|
51
|
-
* Only available for new users or users without existing subscription
|
|
52
|
-
*
|
|
53
|
-
* @default true
|
|
54
|
-
*/
|
|
55
|
-
forNewUsersOnly?: boolean;
|
|
56
50
|
/**
|
|
57
51
|
* A function that will be called when the trial
|
|
58
52
|
* starts.
|
|
@@ -335,13 +329,13 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
335
329
|
body: {
|
|
336
330
|
plan: string;
|
|
337
331
|
metadata?: Record<string, any> | undefined;
|
|
332
|
+
annual?: boolean | undefined;
|
|
338
333
|
referenceId?: string | undefined;
|
|
339
334
|
seats?: number | undefined;
|
|
340
335
|
uiMode?: "embedded" | "hosted" | undefined;
|
|
341
336
|
successUrl?: string | undefined;
|
|
342
337
|
cancelUrl?: string | undefined;
|
|
343
338
|
returnUrl?: string | undefined;
|
|
344
|
-
withoutTrial?: boolean | undefined;
|
|
345
339
|
disableRedirect?: boolean | undefined;
|
|
346
340
|
};
|
|
347
341
|
method?: "POST" | undefined;
|
|
@@ -511,6 +505,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
511
505
|
method: "POST";
|
|
512
506
|
body: z.ZodObject<{
|
|
513
507
|
plan: z.ZodString;
|
|
508
|
+
annual: z.ZodOptional<z.ZodBoolean>;
|
|
514
509
|
referenceId: z.ZodOptional<z.ZodString>;
|
|
515
510
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
516
511
|
seats: z.ZodOptional<z.ZodNumber>;
|
|
@@ -518,7 +513,6 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
518
513
|
successUrl: z.ZodDefault<z.ZodString>;
|
|
519
514
|
cancelUrl: z.ZodDefault<z.ZodString>;
|
|
520
515
|
returnUrl: z.ZodOptional<z.ZodString>;
|
|
521
|
-
withoutTrial: z.ZodOptional<z.ZodBoolean>;
|
|
522
516
|
disableRedirect: z.ZodDefault<z.ZodBoolean>;
|
|
523
517
|
}, "strip", z.ZodTypeAny, {
|
|
524
518
|
plan: string;
|
|
@@ -527,20 +521,20 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
527
521
|
cancelUrl: string;
|
|
528
522
|
disableRedirect: boolean;
|
|
529
523
|
metadata?: Record<string, any> | undefined;
|
|
524
|
+
annual?: boolean | undefined;
|
|
530
525
|
referenceId?: string | undefined;
|
|
531
526
|
seats?: number | undefined;
|
|
532
527
|
returnUrl?: string | undefined;
|
|
533
|
-
withoutTrial?: boolean | undefined;
|
|
534
528
|
}, {
|
|
535
529
|
plan: string;
|
|
536
530
|
metadata?: Record<string, any> | undefined;
|
|
531
|
+
annual?: boolean | undefined;
|
|
537
532
|
referenceId?: string | undefined;
|
|
538
533
|
seats?: number | undefined;
|
|
539
534
|
uiMode?: "embedded" | "hosted" | undefined;
|
|
540
535
|
successUrl?: string | undefined;
|
|
541
536
|
cancelUrl?: string | undefined;
|
|
542
537
|
returnUrl?: string | undefined;
|
|
543
|
-
withoutTrial?: boolean | undefined;
|
|
544
538
|
disableRedirect?: boolean | undefined;
|
|
545
539
|
}>;
|
|
546
540
|
use: (((inputContext: {
|
package/dist/index.d.ts
CHANGED
|
@@ -47,12 +47,6 @@ type Plan = {
|
|
|
47
47
|
* Number of days
|
|
48
48
|
*/
|
|
49
49
|
days: number;
|
|
50
|
-
/**
|
|
51
|
-
* Only available for new users or users without existing subscription
|
|
52
|
-
*
|
|
53
|
-
* @default true
|
|
54
|
-
*/
|
|
55
|
-
forNewUsersOnly?: boolean;
|
|
56
50
|
/**
|
|
57
51
|
* A function that will be called when the trial
|
|
58
52
|
* starts.
|
|
@@ -335,13 +329,13 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
335
329
|
body: {
|
|
336
330
|
plan: string;
|
|
337
331
|
metadata?: Record<string, any> | undefined;
|
|
332
|
+
annual?: boolean | undefined;
|
|
338
333
|
referenceId?: string | undefined;
|
|
339
334
|
seats?: number | undefined;
|
|
340
335
|
uiMode?: "embedded" | "hosted" | undefined;
|
|
341
336
|
successUrl?: string | undefined;
|
|
342
337
|
cancelUrl?: string | undefined;
|
|
343
338
|
returnUrl?: string | undefined;
|
|
344
|
-
withoutTrial?: boolean | undefined;
|
|
345
339
|
disableRedirect?: boolean | undefined;
|
|
346
340
|
};
|
|
347
341
|
method?: "POST" | undefined;
|
|
@@ -511,6 +505,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
511
505
|
method: "POST";
|
|
512
506
|
body: z.ZodObject<{
|
|
513
507
|
plan: z.ZodString;
|
|
508
|
+
annual: z.ZodOptional<z.ZodBoolean>;
|
|
514
509
|
referenceId: z.ZodOptional<z.ZodString>;
|
|
515
510
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
516
511
|
seats: z.ZodOptional<z.ZodNumber>;
|
|
@@ -518,7 +513,6 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
518
513
|
successUrl: z.ZodDefault<z.ZodString>;
|
|
519
514
|
cancelUrl: z.ZodDefault<z.ZodString>;
|
|
520
515
|
returnUrl: z.ZodOptional<z.ZodString>;
|
|
521
|
-
withoutTrial: z.ZodOptional<z.ZodBoolean>;
|
|
522
516
|
disableRedirect: z.ZodDefault<z.ZodBoolean>;
|
|
523
517
|
}, "strip", z.ZodTypeAny, {
|
|
524
518
|
plan: string;
|
|
@@ -527,20 +521,20 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
527
521
|
cancelUrl: string;
|
|
528
522
|
disableRedirect: boolean;
|
|
529
523
|
metadata?: Record<string, any> | undefined;
|
|
524
|
+
annual?: boolean | undefined;
|
|
530
525
|
referenceId?: string | undefined;
|
|
531
526
|
seats?: number | undefined;
|
|
532
527
|
returnUrl?: string | undefined;
|
|
533
|
-
withoutTrial?: boolean | undefined;
|
|
534
528
|
}, {
|
|
535
529
|
plan: string;
|
|
536
530
|
metadata?: Record<string, any> | undefined;
|
|
531
|
+
annual?: boolean | undefined;
|
|
537
532
|
referenceId?: string | undefined;
|
|
538
533
|
seats?: number | undefined;
|
|
539
534
|
uiMode?: "embedded" | "hosted" | undefined;
|
|
540
535
|
successUrl?: string | undefined;
|
|
541
536
|
cancelUrl?: string | undefined;
|
|
542
537
|
returnUrl?: string | undefined;
|
|
543
|
-
withoutTrial?: boolean | undefined;
|
|
544
538
|
disableRedirect?: boolean | undefined;
|
|
545
539
|
}>;
|
|
546
540
|
use: (((inputContext: {
|
package/dist/index.mjs
CHANGED
|
@@ -49,6 +49,7 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
|
|
|
49
49
|
periodEnd: new Date(subscription.current_period_end * 1e3),
|
|
50
50
|
stripeSubscriptionId: checkoutSession.subscription,
|
|
51
51
|
seats,
|
|
52
|
+
stripeCustomerId: subscription.customer.toString(),
|
|
52
53
|
...trial
|
|
53
54
|
},
|
|
54
55
|
where: [
|
|
@@ -125,6 +126,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
125
126
|
periodStart: new Date(subscriptionUpdated.current_period_start * 1e3),
|
|
126
127
|
periodEnd: new Date(subscriptionUpdated.current_period_end * 1e3),
|
|
127
128
|
cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
|
|
129
|
+
stripeCustomerId: subscriptionUpdated.customer.toString(),
|
|
128
130
|
seats,
|
|
129
131
|
stripeSubscriptionId: subscriptionUpdated.id
|
|
130
132
|
},
|
|
@@ -173,40 +175,38 @@ async function onSubscriptionDeleted(ctx, options, event) {
|
|
|
173
175
|
try {
|
|
174
176
|
const subscriptionDeleted = event.data.object;
|
|
175
177
|
const subscriptionId = subscriptionDeleted.id;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
const subscription = await ctx.context.adapter.findOne({
|
|
179
|
+
model: "subscription",
|
|
180
|
+
where: [
|
|
181
|
+
{
|
|
182
|
+
field: "stripeSubscriptionId",
|
|
183
|
+
value: subscriptionId
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
if (subscription) {
|
|
188
|
+
await ctx.context.adapter.update({
|
|
178
189
|
model: "subscription",
|
|
179
190
|
where: [
|
|
180
191
|
{
|
|
181
192
|
field: "stripeSubscriptionId",
|
|
182
193
|
value: subscriptionId
|
|
183
194
|
}
|
|
184
|
-
]
|
|
195
|
+
],
|
|
196
|
+
update: {
|
|
197
|
+
status: "canceled",
|
|
198
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
199
|
+
}
|
|
185
200
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
update: {
|
|
196
|
-
status: "canceled",
|
|
197
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
await options.subscription.onSubscriptionDeleted?.({
|
|
201
|
-
event,
|
|
202
|
-
stripeSubscription: subscriptionDeleted,
|
|
203
|
-
subscription
|
|
204
|
-
});
|
|
205
|
-
} else {
|
|
206
|
-
logger.warn(
|
|
207
|
-
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
201
|
+
await options.subscription.onSubscriptionDeleted?.({
|
|
202
|
+
event,
|
|
203
|
+
stripeSubscription: subscriptionDeleted,
|
|
204
|
+
subscription
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
logger.warn(
|
|
208
|
+
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`
|
|
209
|
+
);
|
|
210
210
|
}
|
|
211
211
|
} catch (error) {
|
|
212
212
|
logger.error(`Stripe webhook failed. Error: ${error}`);
|
|
@@ -312,7 +312,12 @@ const stripe = (options) => {
|
|
|
312
312
|
{
|
|
313
313
|
method: "POST",
|
|
314
314
|
body: z.object({
|
|
315
|
-
plan: z.string(
|
|
315
|
+
plan: z.string({
|
|
316
|
+
description: "The name of the plan to upgrade to"
|
|
317
|
+
}),
|
|
318
|
+
annual: z.boolean({
|
|
319
|
+
description: "Whether to upgrade to an annual plan"
|
|
320
|
+
}).optional(),
|
|
316
321
|
referenceId: z.string().optional(),
|
|
317
322
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
318
323
|
seats: z.number({
|
|
@@ -326,7 +331,6 @@ const stripe = (options) => {
|
|
|
326
331
|
description: "callback url to redirect back after successful subscription"
|
|
327
332
|
}).default("/"),
|
|
328
333
|
returnUrl: z.string().optional(),
|
|
329
|
-
withoutTrial: z.boolean().optional(),
|
|
330
334
|
disableRedirect: z.boolean().default(false)
|
|
331
335
|
}),
|
|
332
336
|
use: [
|
|
@@ -403,6 +407,11 @@ const stripe = (options) => {
|
|
|
403
407
|
const existingSubscription = subscriptions.find(
|
|
404
408
|
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
405
409
|
);
|
|
410
|
+
if (existingSubscription && existingSubscription.status === "active" && existingSubscription.plan === ctx.body.plan) {
|
|
411
|
+
throw new APIError("BAD_REQUEST", {
|
|
412
|
+
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
|
|
413
|
+
});
|
|
414
|
+
}
|
|
406
415
|
if (activeSubscription && customerId) {
|
|
407
416
|
const { url } = await client.billingPortal.sessions.create({
|
|
408
417
|
customer: customerId,
|
|
@@ -415,35 +424,12 @@ const stripe = (options) => {
|
|
|
415
424
|
{
|
|
416
425
|
id: activeSubscription.items.data[0]?.id,
|
|
417
426
|
quantity: 1,
|
|
418
|
-
price: plan.priceId
|
|
427
|
+
price: ctx.body.annual ? plan.annualDiscountPriceId : plan.priceId
|
|
419
428
|
}
|
|
420
429
|
]
|
|
421
430
|
}
|
|
422
431
|
}
|
|
423
432
|
}).catch(async (e) => {
|
|
424
|
-
if (e.message.includes("no changes")) {
|
|
425
|
-
const plan2 = await getPlanByPriceId(
|
|
426
|
-
options,
|
|
427
|
-
activeSubscription.items.data[0]?.plan.id
|
|
428
|
-
);
|
|
429
|
-
await ctx.context.adapter.update({
|
|
430
|
-
model: "subscription",
|
|
431
|
-
update: {
|
|
432
|
-
status: activeSubscription.status,
|
|
433
|
-
seats: activeSubscription.items.data[0]?.quantity,
|
|
434
|
-
plan: plan2?.name.toLowerCase()
|
|
435
|
-
},
|
|
436
|
-
where: [
|
|
437
|
-
{
|
|
438
|
-
field: "referenceId",
|
|
439
|
-
value: referenceId
|
|
440
|
-
}
|
|
441
|
-
]
|
|
442
|
-
});
|
|
443
|
-
throw new APIError("BAD_REQUEST", {
|
|
444
|
-
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
433
|
throw ctx.error("BAD_REQUEST", {
|
|
448
434
|
message: e.message,
|
|
449
435
|
code: e.code
|
|
@@ -454,11 +440,6 @@ const stripe = (options) => {
|
|
|
454
440
|
redirect: true
|
|
455
441
|
});
|
|
456
442
|
}
|
|
457
|
-
if (existingSubscription && existingSubscription.status === "active" && existingSubscription.plan === ctx.body.plan) {
|
|
458
|
-
throw new APIError("BAD_REQUEST", {
|
|
459
|
-
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
443
|
let subscription = existingSubscription;
|
|
463
444
|
if (!subscription) {
|
|
464
445
|
const newSubscription = await ctx.context.adapter.create({
|
|
@@ -486,39 +467,48 @@ const stripe = (options) => {
|
|
|
486
467
|
},
|
|
487
468
|
ctx.request
|
|
488
469
|
);
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
470
|
+
const freeTrail = plan.freeTrial ? {
|
|
471
|
+
trial_period_days: plan.freeTrial.days
|
|
472
|
+
} : void 0;
|
|
473
|
+
const checkoutSession = await client.checkout.sessions.create(
|
|
474
|
+
{
|
|
475
|
+
...customerId ? {
|
|
476
|
+
customer: customerId,
|
|
477
|
+
customer_update: {
|
|
478
|
+
name: "auto",
|
|
479
|
+
address: "auto"
|
|
480
|
+
}
|
|
481
|
+
} : {
|
|
482
|
+
customer_email: session.user.email
|
|
483
|
+
},
|
|
484
|
+
success_url: getUrl(
|
|
485
|
+
ctx,
|
|
486
|
+
`${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(
|
|
487
|
+
ctx.body.successUrl
|
|
488
|
+
)}&reference=${encodeURIComponent(referenceId)}`
|
|
489
|
+
),
|
|
490
|
+
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
491
|
+
line_items: [
|
|
492
|
+
{
|
|
493
|
+
price: ctx.body.annual ? plan.annualDiscountPriceId : plan.priceId,
|
|
494
|
+
quantity: ctx.body.seats || 1
|
|
495
|
+
}
|
|
496
|
+
],
|
|
497
|
+
subscription_data: {
|
|
498
|
+
...freeTrail
|
|
499
|
+
},
|
|
500
|
+
mode: "subscription",
|
|
501
|
+
client_reference_id: referenceId,
|
|
502
|
+
...params?.params,
|
|
503
|
+
metadata: {
|
|
504
|
+
userId: user.id,
|
|
505
|
+
subscriptionId: subscription.id,
|
|
506
|
+
referenceId,
|
|
507
|
+
...params?.params?.metadata
|
|
495
508
|
}
|
|
496
|
-
} : {
|
|
497
|
-
customer_email: session.user.email
|
|
498
509
|
},
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
`${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(
|
|
502
|
-
ctx.body.successUrl
|
|
503
|
-
)}&reference=${encodeURIComponent(referenceId)}`
|
|
504
|
-
),
|
|
505
|
-
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
506
|
-
line_items: [
|
|
507
|
-
{
|
|
508
|
-
price: plan.priceId,
|
|
509
|
-
quantity: ctx.body.seats || 1
|
|
510
|
-
}
|
|
511
|
-
],
|
|
512
|
-
mode: "subscription",
|
|
513
|
-
client_reference_id: referenceId,
|
|
514
|
-
...params,
|
|
515
|
-
metadata: {
|
|
516
|
-
userId: user.id,
|
|
517
|
-
subscriptionId: subscription.id,
|
|
518
|
-
referenceId,
|
|
519
|
-
...params?.params?.metadata
|
|
520
|
-
}
|
|
521
|
-
}).catch(async (e) => {
|
|
510
|
+
params?.options
|
|
511
|
+
).catch(async (e) => {
|
|
522
512
|
throw ctx.error("BAD_REQUEST", {
|
|
523
513
|
message: e.message,
|
|
524
514
|
code: e.code
|
|
@@ -630,10 +620,30 @@ const stripe = (options) => {
|
|
|
630
620
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
631
621
|
});
|
|
632
622
|
}
|
|
633
|
-
const
|
|
634
|
-
customer: subscription.stripeCustomerId
|
|
635
|
-
|
|
636
|
-
|
|
623
|
+
const activeSubscriptions = await client.subscriptions.list({
|
|
624
|
+
customer: subscription.stripeCustomerId
|
|
625
|
+
}).then(
|
|
626
|
+
(res) => res.data.filter(
|
|
627
|
+
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
628
|
+
)
|
|
629
|
+
);
|
|
630
|
+
if (!activeSubscriptions.length) {
|
|
631
|
+
await ctx.context.adapter.deleteMany({
|
|
632
|
+
model: "subscription",
|
|
633
|
+
where: [
|
|
634
|
+
{
|
|
635
|
+
field: "referenceId",
|
|
636
|
+
value: referenceId
|
|
637
|
+
}
|
|
638
|
+
]
|
|
639
|
+
});
|
|
640
|
+
throw ctx.error("BAD_REQUEST", {
|
|
641
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
const activeSubscription = activeSubscriptions.find(
|
|
645
|
+
(sub) => sub.id === subscription.stripeSubscriptionId
|
|
646
|
+
);
|
|
637
647
|
if (!activeSubscription) {
|
|
638
648
|
throw ctx.error("BAD_REQUEST", {
|
|
639
649
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/stripe",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.2.2-beta.
|
|
4
|
+
"version": "1.2.2-beta.5",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"zod": "^3.24.1",
|
|
38
|
-
"better-auth": "^1.2.2-beta.
|
|
38
|
+
"better-auth": "^1.2.2-beta.5"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/better-sqlite3": "^7.6.12",
|
package/src/hooks.ts
CHANGED
|
@@ -43,6 +43,7 @@ export async function onCheckoutSessionCompleted(
|
|
|
43
43
|
periodEnd: new Date(subscription.current_period_end * 1000),
|
|
44
44
|
stripeSubscriptionId: checkoutSession.subscription as string,
|
|
45
45
|
seats,
|
|
46
|
+
stripeCustomerId: subscription.customer.toString(),
|
|
46
47
|
...trial,
|
|
47
48
|
},
|
|
48
49
|
where: [
|
|
@@ -132,6 +133,7 @@ export async function onSubscriptionUpdated(
|
|
|
132
133
|
periodStart: new Date(subscriptionUpdated.current_period_start * 1000),
|
|
133
134
|
periodEnd: new Date(subscriptionUpdated.current_period_end * 1000),
|
|
134
135
|
cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
|
|
136
|
+
stripeCustomerId: subscriptionUpdated.customer.toString(),
|
|
135
137
|
seats,
|
|
136
138
|
stripeSubscriptionId: subscriptionUpdated.id,
|
|
137
139
|
},
|
|
@@ -197,8 +199,17 @@ export async function onSubscriptionDeleted(
|
|
|
197
199
|
try {
|
|
198
200
|
const subscriptionDeleted = event.data.object as Stripe.Subscription;
|
|
199
201
|
const subscriptionId = subscriptionDeleted.id;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
const subscription = await ctx.context.adapter.findOne<Subscription>({
|
|
203
|
+
model: "subscription",
|
|
204
|
+
where: [
|
|
205
|
+
{
|
|
206
|
+
field: "stripeSubscriptionId",
|
|
207
|
+
value: subscriptionId,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
if (subscription) {
|
|
212
|
+
await ctx.context.adapter.update({
|
|
202
213
|
model: "subscription",
|
|
203
214
|
where: [
|
|
204
215
|
{
|
|
@@ -206,31 +217,20 @@ export async function onSubscriptionDeleted(
|
|
|
206
217
|
value: subscriptionId,
|
|
207
218
|
},
|
|
208
219
|
],
|
|
220
|
+
update: {
|
|
221
|
+
status: "canceled",
|
|
222
|
+
updatedAt: new Date(),
|
|
223
|
+
},
|
|
209
224
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
update: {
|
|
220
|
-
status: "canceled",
|
|
221
|
-
updatedAt: new Date(),
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
await options.subscription.onSubscriptionDeleted?.({
|
|
225
|
-
event,
|
|
226
|
-
stripeSubscription: subscriptionDeleted,
|
|
227
|
-
subscription,
|
|
228
|
-
});
|
|
229
|
-
} else {
|
|
230
|
-
logger.warn(
|
|
231
|
-
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
225
|
+
await options.subscription.onSubscriptionDeleted?.({
|
|
226
|
+
event,
|
|
227
|
+
stripeSubscription: subscriptionDeleted,
|
|
228
|
+
subscription,
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
logger.warn(
|
|
232
|
+
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`,
|
|
233
|
+
);
|
|
234
234
|
}
|
|
235
235
|
} catch (error: any) {
|
|
236
236
|
logger.error(`Stripe webhook failed. Error: ${error}`);
|
package/src/index.ts
CHANGED
|
@@ -77,7 +77,14 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
77
77
|
{
|
|
78
78
|
method: "POST",
|
|
79
79
|
body: z.object({
|
|
80
|
-
plan: z.string(
|
|
80
|
+
plan: z.string({
|
|
81
|
+
description: "The name of the plan to upgrade to",
|
|
82
|
+
}),
|
|
83
|
+
annual: z
|
|
84
|
+
.boolean({
|
|
85
|
+
description: "Whether to upgrade to an annual plan",
|
|
86
|
+
})
|
|
87
|
+
.optional(),
|
|
81
88
|
referenceId: z.string().optional(),
|
|
82
89
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
83
90
|
seats: z
|
|
@@ -99,7 +106,6 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
99
106
|
})
|
|
100
107
|
.default("/"),
|
|
101
108
|
returnUrl: z.string().optional(),
|
|
102
|
-
withoutTrial: z.boolean().optional(),
|
|
103
109
|
disableRedirect: z.boolean().default(false),
|
|
104
110
|
}),
|
|
105
111
|
use: [
|
|
@@ -182,9 +188,21 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
182
188
|
},
|
|
183
189
|
],
|
|
184
190
|
});
|
|
191
|
+
|
|
185
192
|
const existingSubscription = subscriptions.find(
|
|
186
193
|
(sub) => sub.status === "active" || sub.status === "trialing",
|
|
187
194
|
);
|
|
195
|
+
|
|
196
|
+
if (
|
|
197
|
+
existingSubscription &&
|
|
198
|
+
existingSubscription.status === "active" &&
|
|
199
|
+
existingSubscription.plan === ctx.body.plan
|
|
200
|
+
) {
|
|
201
|
+
throw new APIError("BAD_REQUEST", {
|
|
202
|
+
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
188
206
|
if (activeSubscription && customerId) {
|
|
189
207
|
const { url } = await client.billingPortal.sessions
|
|
190
208
|
.create({
|
|
@@ -198,40 +216,15 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
198
216
|
{
|
|
199
217
|
id: activeSubscription.items.data[0]?.id as string,
|
|
200
218
|
quantity: 1,
|
|
201
|
-
price:
|
|
219
|
+
price: ctx.body.annual
|
|
220
|
+
? plan.annualDiscountPriceId
|
|
221
|
+
: plan.priceId,
|
|
202
222
|
},
|
|
203
223
|
],
|
|
204
224
|
},
|
|
205
225
|
},
|
|
206
226
|
})
|
|
207
227
|
.catch(async (e) => {
|
|
208
|
-
if (e.message.includes("no changes")) {
|
|
209
|
-
/**
|
|
210
|
-
* If the subscription is already active on stripe, we need to
|
|
211
|
-
* update the status to the new status.
|
|
212
|
-
*/
|
|
213
|
-
const plan = await getPlanByPriceId(
|
|
214
|
-
options,
|
|
215
|
-
activeSubscription.items.data[0]?.plan.id,
|
|
216
|
-
);
|
|
217
|
-
await ctx.context.adapter.update({
|
|
218
|
-
model: "subscription",
|
|
219
|
-
update: {
|
|
220
|
-
status: activeSubscription.status,
|
|
221
|
-
seats: activeSubscription.items.data[0]?.quantity,
|
|
222
|
-
plan: plan?.name.toLowerCase(),
|
|
223
|
-
},
|
|
224
|
-
where: [
|
|
225
|
-
{
|
|
226
|
-
field: "referenceId",
|
|
227
|
-
value: referenceId,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
});
|
|
231
|
-
throw new APIError("BAD_REQUEST", {
|
|
232
|
-
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
228
|
throw ctx.error("BAD_REQUEST", {
|
|
236
229
|
message: e.message,
|
|
237
230
|
code: e.code,
|
|
@@ -243,15 +236,6 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
243
236
|
});
|
|
244
237
|
}
|
|
245
238
|
|
|
246
|
-
if (
|
|
247
|
-
existingSubscription &&
|
|
248
|
-
existingSubscription.status === "active" &&
|
|
249
|
-
existingSubscription.plan === ctx.body.plan
|
|
250
|
-
) {
|
|
251
|
-
throw new APIError("BAD_REQUEST", {
|
|
252
|
-
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
239
|
let subscription = existingSubscription;
|
|
256
240
|
if (!subscription) {
|
|
257
241
|
const newSubscription = await ctx.context.adapter.create<
|
|
@@ -285,44 +269,58 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
285
269
|
ctx.request,
|
|
286
270
|
);
|
|
287
271
|
|
|
272
|
+
const freeTrail = plan.freeTrial
|
|
273
|
+
? {
|
|
274
|
+
trial_period_days: plan.freeTrial.days,
|
|
275
|
+
}
|
|
276
|
+
: undefined;
|
|
277
|
+
|
|
288
278
|
const checkoutSession = await client.checkout.sessions
|
|
289
|
-
.create(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
279
|
+
.create(
|
|
280
|
+
{
|
|
281
|
+
...(customerId
|
|
282
|
+
? {
|
|
283
|
+
customer: customerId,
|
|
284
|
+
customer_update: {
|
|
285
|
+
name: "auto",
|
|
286
|
+
address: "auto",
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
: {
|
|
290
|
+
customer_email: session.user.email,
|
|
291
|
+
}),
|
|
292
|
+
success_url: getUrl(
|
|
293
|
+
ctx,
|
|
294
|
+
`${
|
|
295
|
+
ctx.context.baseURL
|
|
296
|
+
}/subscription/success?callbackURL=${encodeURIComponent(
|
|
297
|
+
ctx.body.successUrl,
|
|
298
|
+
)}&reference=${encodeURIComponent(referenceId)}`,
|
|
299
|
+
),
|
|
300
|
+
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
301
|
+
line_items: [
|
|
302
|
+
{
|
|
303
|
+
price: ctx.body.annual
|
|
304
|
+
? plan.annualDiscountPriceId
|
|
305
|
+
: plan.priceId,
|
|
306
|
+
quantity: ctx.body.seats || 1,
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
subscription_data: {
|
|
310
|
+
...freeTrail,
|
|
311
|
+
},
|
|
312
|
+
mode: "subscription",
|
|
313
|
+
client_reference_id: referenceId,
|
|
314
|
+
...params?.params,
|
|
315
|
+
metadata: {
|
|
316
|
+
userId: user.id,
|
|
317
|
+
subscriptionId: subscription.id,
|
|
318
|
+
referenceId,
|
|
319
|
+
...params?.params?.metadata,
|
|
314
320
|
},
|
|
315
|
-
],
|
|
316
|
-
mode: "subscription",
|
|
317
|
-
client_reference_id: referenceId,
|
|
318
|
-
...params,
|
|
319
|
-
metadata: {
|
|
320
|
-
userId: user.id,
|
|
321
|
-
subscriptionId: subscription.id,
|
|
322
|
-
referenceId,
|
|
323
|
-
...params?.params?.metadata,
|
|
324
321
|
},
|
|
325
|
-
|
|
322
|
+
params?.options,
|
|
323
|
+
)
|
|
326
324
|
.catch(async (e) => {
|
|
327
325
|
throw ctx.error("BAD_REQUEST", {
|
|
328
326
|
message: e.message,
|
|
@@ -443,12 +441,36 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
443
441
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
444
442
|
});
|
|
445
443
|
}
|
|
446
|
-
const
|
|
444
|
+
const activeSubscriptions = await client.subscriptions
|
|
447
445
|
.list({
|
|
448
446
|
customer: subscription.stripeCustomerId,
|
|
449
|
-
status: "active",
|
|
450
447
|
})
|
|
451
|
-
.then((res) =>
|
|
448
|
+
.then((res) =>
|
|
449
|
+
res.data.filter(
|
|
450
|
+
(sub) => sub.status === "active" || sub.status === "trialing",
|
|
451
|
+
),
|
|
452
|
+
);
|
|
453
|
+
if (!activeSubscriptions.length) {
|
|
454
|
+
/**
|
|
455
|
+
* If the subscription is not found, we need to delete the subscription
|
|
456
|
+
* from the database. This is a rare case and should not happen.
|
|
457
|
+
*/
|
|
458
|
+
await ctx.context.adapter.deleteMany({
|
|
459
|
+
model: "subscription",
|
|
460
|
+
where: [
|
|
461
|
+
{
|
|
462
|
+
field: "referenceId",
|
|
463
|
+
value: referenceId,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
});
|
|
467
|
+
throw ctx.error("BAD_REQUEST", {
|
|
468
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const activeSubscription = activeSubscriptions.find(
|
|
472
|
+
(sub) => sub.id === subscription.stripeSubscriptionId,
|
|
473
|
+
);
|
|
452
474
|
if (!activeSubscription) {
|
|
453
475
|
throw ctx.error("BAD_REQUEST", {
|
|
454
476
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
package/src/types.ts
CHANGED
|
@@ -43,12 +43,6 @@ export type Plan = {
|
|
|
43
43
|
* Number of days
|
|
44
44
|
*/
|
|
45
45
|
days: number;
|
|
46
|
-
/**
|
|
47
|
-
* Only available for new users or users without existing subscription
|
|
48
|
-
*
|
|
49
|
-
* @default true
|
|
50
|
-
*/
|
|
51
|
-
forNewUsersOnly?: boolean;
|
|
52
46
|
/**
|
|
53
47
|
* A function that will be called when the trial
|
|
54
48
|
* starts.
|