@better-auth/stripe 1.2.3 → 1.2.4-beta.10
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 +4 -4
- package/dist/client.d.cts +2 -2
- package/dist/client.d.mts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/index.cjs +134 -63
- package/dist/index.d.cts +72 -8
- package/dist/index.d.mts +72 -8
- package/dist/index.d.ts +72 -8
- package/dist/index.mjs +135 -64
- package/package.json +2 -2
- package/src/client.ts +2 -2
- package/src/hooks.ts +3 -3
- package/src/index.ts +172 -72
- package/src/stripe.test.ts +15 -7
- package/src/types.ts +11 -4
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { logger } from 'better-auth';
|
|
1
2
|
import { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins';
|
|
2
3
|
import { z } from 'zod';
|
|
3
|
-
import { getSessionFromCtx, sessionMiddleware,
|
|
4
|
+
import { originCheck, getSessionFromCtx, sessionMiddleware, APIError } from 'better-auth/api';
|
|
4
5
|
import { generateRandomString } from 'better-auth/crypto';
|
|
5
|
-
import { logger } from 'better-auth';
|
|
6
6
|
|
|
7
7
|
async function getPlans(options) {
|
|
8
8
|
return typeof options?.subscription?.plans === "function" ? await options.subscription?.plans() : options.subscription?.plans;
|
|
@@ -93,11 +93,11 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
93
93
|
const subscriptionUpdated = event.data.object;
|
|
94
94
|
const priceId = subscriptionUpdated.items.data[0].price.id;
|
|
95
95
|
const plan = await getPlanByPriceId(options, priceId);
|
|
96
|
-
const
|
|
96
|
+
const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
|
|
97
97
|
const customerId = subscriptionUpdated.customer?.toString();
|
|
98
98
|
let subscription = await ctx.context.adapter.findOne({
|
|
99
99
|
model: "subscription",
|
|
100
|
-
where:
|
|
100
|
+
where: subscriptionId ? [{ field: "id", value: subscriptionId }] : [{ field: "stripeSubscriptionId", value: subscriptionUpdated.id }]
|
|
101
101
|
});
|
|
102
102
|
if (!subscription) {
|
|
103
103
|
const subs = await ctx.context.adapter.findMany({
|
|
@@ -311,25 +311,64 @@ const stripe = (options) => {
|
|
|
311
311
|
{
|
|
312
312
|
method: "POST",
|
|
313
313
|
body: z.object({
|
|
314
|
+
/**
|
|
315
|
+
* The name of the plan to subscribe
|
|
316
|
+
*/
|
|
314
317
|
plan: z.string({
|
|
315
318
|
description: "The name of the plan to upgrade to"
|
|
316
319
|
}),
|
|
320
|
+
/**
|
|
321
|
+
* If annual plan should be applied.
|
|
322
|
+
*/
|
|
317
323
|
annual: z.boolean({
|
|
318
324
|
description: "Whether to upgrade to an annual plan"
|
|
319
325
|
}).optional(),
|
|
320
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Reference id of the subscription to upgrade
|
|
328
|
+
* This is used to identify the subscription to upgrade
|
|
329
|
+
* If not provided, the user's id will be used
|
|
330
|
+
*/
|
|
331
|
+
referenceId: z.string({
|
|
332
|
+
description: "Reference id of the subscription to upgrade"
|
|
333
|
+
}).optional(),
|
|
334
|
+
/**
|
|
335
|
+
* This is to allow a specific subscription to be upgrade.
|
|
336
|
+
* If subscription id is provided, and subscription isn't found,
|
|
337
|
+
* it'll throw an error.
|
|
338
|
+
*/
|
|
339
|
+
subscriptionId: z.string({
|
|
340
|
+
description: "The id of the subscription to upgrade"
|
|
341
|
+
}).optional(),
|
|
342
|
+
/**
|
|
343
|
+
* Any additional data you want to store in your database
|
|
344
|
+
* subscriptions
|
|
345
|
+
*/
|
|
321
346
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
347
|
+
/**
|
|
348
|
+
* If a subscription
|
|
349
|
+
*/
|
|
322
350
|
seats: z.number({
|
|
323
351
|
description: "Number of seats to upgrade to (if applicable)"
|
|
324
352
|
}).optional(),
|
|
325
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Success url to redirect back after successful subscription
|
|
355
|
+
*/
|
|
326
356
|
successUrl: z.string({
|
|
327
357
|
description: "callback url to redirect back after successful subscription"
|
|
328
358
|
}).default("/"),
|
|
359
|
+
/**
|
|
360
|
+
* Cancel URL
|
|
361
|
+
*/
|
|
329
362
|
cancelUrl: z.string({
|
|
330
363
|
description: "callback url to redirect back after successful subscription"
|
|
331
364
|
}).default("/"),
|
|
365
|
+
/**
|
|
366
|
+
* Return URL
|
|
367
|
+
*/
|
|
332
368
|
returnUrl: z.string().optional(),
|
|
369
|
+
/**
|
|
370
|
+
* Disable Redirect
|
|
371
|
+
*/
|
|
333
372
|
disableRedirect: z.boolean().default(false)
|
|
334
373
|
}),
|
|
335
374
|
use: [
|
|
@@ -354,7 +393,16 @@ const stripe = (options) => {
|
|
|
354
393
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND
|
|
355
394
|
});
|
|
356
395
|
}
|
|
357
|
-
|
|
396
|
+
const subscriptionToUpdate = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
|
|
397
|
+
model: "subscription",
|
|
398
|
+
where: [{ field: "id", value: ctx.body.subscriptionId }]
|
|
399
|
+
}) : null;
|
|
400
|
+
if (ctx.body.subscriptionId && !subscriptionToUpdate) {
|
|
401
|
+
throw new APIError("BAD_REQUEST", {
|
|
402
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
let customerId = subscriptionToUpdate?.stripeCustomerId || user.stripeCustomerId;
|
|
358
406
|
if (!customerId) {
|
|
359
407
|
try {
|
|
360
408
|
const stripeCustomer = await client.customers.create(
|
|
@@ -394,7 +442,7 @@ const stripe = (options) => {
|
|
|
394
442
|
customer: customerId,
|
|
395
443
|
status: "active"
|
|
396
444
|
}).then((res) => res.data[0]).catch((e) => null) : null;
|
|
397
|
-
const subscriptions = await ctx.context.adapter.findMany({
|
|
445
|
+
const subscriptions = subscriptionToUpdate ? [subscriptionToUpdate] : await ctx.context.adapter.findMany({
|
|
398
446
|
model: "subscription",
|
|
399
447
|
where: [
|
|
400
448
|
{
|
|
@@ -484,7 +532,7 @@ const stripe = (options) => {
|
|
|
484
532
|
ctx,
|
|
485
533
|
`${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(
|
|
486
534
|
ctx.body.successUrl
|
|
487
|
-
)}&
|
|
535
|
+
)}&subscriptionId=${encodeURIComponent(subscription.id)}`
|
|
488
536
|
),
|
|
489
537
|
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
490
538
|
line_items: [
|
|
@@ -523,10 +571,11 @@ const stripe = (options) => {
|
|
|
523
571
|
"/subscription/cancel/callback",
|
|
524
572
|
{
|
|
525
573
|
method: "GET",
|
|
526
|
-
query: z.record(z.string(), z.any()).optional()
|
|
574
|
+
query: z.record(z.string(), z.any()).optional(),
|
|
575
|
+
use: [originCheck((ctx) => ctx.query.callbackURL)]
|
|
527
576
|
},
|
|
528
577
|
async (ctx) => {
|
|
529
|
-
if (!ctx.query || !ctx.query.callbackURL || !ctx.query.
|
|
578
|
+
if (!ctx.query || !ctx.query.callbackURL || !ctx.query.subscriptionId) {
|
|
530
579
|
throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
|
|
531
580
|
}
|
|
532
581
|
const session = await getSessionFromCtx(
|
|
@@ -536,15 +585,15 @@ const stripe = (options) => {
|
|
|
536
585
|
throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
|
|
537
586
|
}
|
|
538
587
|
const { user } = session;
|
|
539
|
-
const { callbackURL,
|
|
588
|
+
const { callbackURL, subscriptionId } = ctx.query;
|
|
540
589
|
if (user?.stripeCustomerId) {
|
|
541
590
|
try {
|
|
542
591
|
const subscription = await ctx.context.adapter.findOne({
|
|
543
592
|
model: "subscription",
|
|
544
593
|
where: [
|
|
545
594
|
{
|
|
546
|
-
field: "
|
|
547
|
-
value:
|
|
595
|
+
field: "id",
|
|
596
|
+
value: subscriptionId
|
|
548
597
|
}
|
|
549
598
|
]
|
|
550
599
|
});
|
|
@@ -567,8 +616,8 @@ const stripe = (options) => {
|
|
|
567
616
|
},
|
|
568
617
|
where: [
|
|
569
618
|
{
|
|
570
|
-
field: "
|
|
571
|
-
value:
|
|
619
|
+
field: "id",
|
|
620
|
+
value: subscription.id
|
|
572
621
|
}
|
|
573
622
|
]
|
|
574
623
|
});
|
|
@@ -595,6 +644,7 @@ const stripe = (options) => {
|
|
|
595
644
|
method: "POST",
|
|
596
645
|
body: z.object({
|
|
597
646
|
referenceId: z.string().optional(),
|
|
647
|
+
subscriptionId: z.string().optional(),
|
|
598
648
|
returnUrl: z.string()
|
|
599
649
|
}),
|
|
600
650
|
use: [
|
|
@@ -605,15 +655,22 @@ const stripe = (options) => {
|
|
|
605
655
|
},
|
|
606
656
|
async (ctx) => {
|
|
607
657
|
const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
|
|
608
|
-
const subscription = await ctx.context.adapter.findOne({
|
|
658
|
+
const subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
|
|
609
659
|
model: "subscription",
|
|
610
660
|
where: [
|
|
611
661
|
{
|
|
612
|
-
field: "
|
|
613
|
-
value:
|
|
662
|
+
field: "id",
|
|
663
|
+
value: ctx.body.subscriptionId
|
|
614
664
|
}
|
|
615
665
|
]
|
|
616
|
-
})
|
|
666
|
+
}) : await ctx.context.adapter.findMany({
|
|
667
|
+
model: "subscription",
|
|
668
|
+
where: [{ field: "referenceId", value: referenceId }]
|
|
669
|
+
}).then(
|
|
670
|
+
(subs) => subs.find(
|
|
671
|
+
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
672
|
+
)
|
|
673
|
+
);
|
|
617
674
|
if (!subscription || !subscription.stripeCustomerId) {
|
|
618
675
|
throw ctx.error("BAD_REQUEST", {
|
|
619
676
|
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
@@ -654,7 +711,7 @@ const stripe = (options) => {
|
|
|
654
711
|
ctx,
|
|
655
712
|
`${ctx.context.baseURL}/subscription/cancel/callback?callbackURL=${encodeURIComponent(
|
|
656
713
|
ctx.body?.returnUrl || "/"
|
|
657
|
-
)}&
|
|
714
|
+
)}&subscriptionId=${encodeURIComponent(subscription.id)}`
|
|
658
715
|
),
|
|
659
716
|
flow_data: {
|
|
660
717
|
type: "subscription_cancel",
|
|
@@ -736,10 +793,11 @@ const stripe = (options) => {
|
|
|
736
793
|
"/subscription/success",
|
|
737
794
|
{
|
|
738
795
|
method: "GET",
|
|
739
|
-
query: z.record(z.string(), z.any()).optional()
|
|
796
|
+
query: z.record(z.string(), z.any()).optional(),
|
|
797
|
+
use: [originCheck((ctx) => ctx.query.callbackURL)]
|
|
740
798
|
},
|
|
741
799
|
async (ctx) => {
|
|
742
|
-
if (!ctx.query || !ctx.query.callbackURL || !ctx.query.
|
|
800
|
+
if (!ctx.query || !ctx.query.callbackURL || !ctx.query.subscriptionId) {
|
|
743
801
|
throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
|
|
744
802
|
}
|
|
745
803
|
const session = await getSessionFromCtx(
|
|
@@ -749,38 +807,24 @@ const stripe = (options) => {
|
|
|
749
807
|
throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
|
|
750
808
|
}
|
|
751
809
|
const { user } = session;
|
|
752
|
-
const { callbackURL,
|
|
753
|
-
const
|
|
810
|
+
const { callbackURL, subscriptionId } = ctx.query;
|
|
811
|
+
const subscription = await ctx.context.adapter.findOne({
|
|
754
812
|
model: "subscription",
|
|
755
813
|
where: [
|
|
756
814
|
{
|
|
757
|
-
field: "
|
|
758
|
-
value:
|
|
815
|
+
field: "id",
|
|
816
|
+
value: subscriptionId
|
|
759
817
|
}
|
|
760
818
|
]
|
|
761
819
|
});
|
|
762
|
-
|
|
763
|
-
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
764
|
-
);
|
|
765
|
-
if (activeSubscription) {
|
|
820
|
+
if (subscription?.status === "active" || subscription?.status === "trialing") {
|
|
766
821
|
return ctx.redirect(getUrl(ctx, callbackURL));
|
|
767
822
|
}
|
|
768
|
-
|
|
823
|
+
const customerId = subscription?.stripeCustomerId || user.stripeCustomerId;
|
|
824
|
+
if (customerId) {
|
|
769
825
|
try {
|
|
770
|
-
const subscription = await ctx.context.adapter.findOne({
|
|
771
|
-
model: "subscription",
|
|
772
|
-
where: [
|
|
773
|
-
{
|
|
774
|
-
field: "referenceId",
|
|
775
|
-
value: reference
|
|
776
|
-
}
|
|
777
|
-
]
|
|
778
|
-
});
|
|
779
|
-
if (!subscription || subscription.status === "active") {
|
|
780
|
-
throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
781
|
-
}
|
|
782
826
|
const stripeSubscription = await client.subscriptions.list({
|
|
783
|
-
customer:
|
|
827
|
+
customer: customerId,
|
|
784
828
|
status: "active"
|
|
785
829
|
}).then((res) => res.data[0]);
|
|
786
830
|
if (stripeSubscription) {
|
|
@@ -788,21 +832,33 @@ const stripe = (options) => {
|
|
|
788
832
|
options,
|
|
789
833
|
stripeSubscription.items.data[0]?.plan.id
|
|
790
834
|
);
|
|
791
|
-
if (plan &&
|
|
835
|
+
if (plan && subscription) {
|
|
792
836
|
await ctx.context.adapter.update({
|
|
793
837
|
model: "subscription",
|
|
794
838
|
update: {
|
|
795
839
|
status: stripeSubscription.status,
|
|
796
840
|
seats: stripeSubscription.items.data[0]?.quantity || 1,
|
|
797
841
|
plan: plan.name.toLowerCase(),
|
|
798
|
-
periodEnd:
|
|
799
|
-
|
|
800
|
-
|
|
842
|
+
periodEnd: new Date(
|
|
843
|
+
stripeSubscription.current_period_end * 1e3
|
|
844
|
+
),
|
|
845
|
+
periodStart: new Date(
|
|
846
|
+
stripeSubscription.current_period_start * 1e3
|
|
847
|
+
),
|
|
848
|
+
stripeSubscriptionId: stripeSubscription.id,
|
|
849
|
+
...stripeSubscription.trial_start && stripeSubscription.trial_end ? {
|
|
850
|
+
trialStart: new Date(
|
|
851
|
+
stripeSubscription.trial_start * 1e3
|
|
852
|
+
),
|
|
853
|
+
trialEnd: new Date(
|
|
854
|
+
stripeSubscription.trial_end * 1e3
|
|
855
|
+
)
|
|
856
|
+
} : {}
|
|
801
857
|
},
|
|
802
858
|
where: [
|
|
803
859
|
{
|
|
804
|
-
field: "
|
|
805
|
-
value:
|
|
860
|
+
field: "id",
|
|
861
|
+
value: subscription.id
|
|
806
862
|
}
|
|
807
863
|
]
|
|
808
864
|
});
|
|
@@ -845,7 +901,11 @@ const stripe = (options) => {
|
|
|
845
901
|
message: "Stripe webhook secret not found"
|
|
846
902
|
});
|
|
847
903
|
}
|
|
848
|
-
event = client.webhooks.
|
|
904
|
+
event = await client.webhooks.constructEventAsync(
|
|
905
|
+
buf,
|
|
906
|
+
sig,
|
|
907
|
+
webhookSecret
|
|
908
|
+
);
|
|
849
909
|
} catch (err) {
|
|
850
910
|
ctx.context.logger.error(`${err.message}`);
|
|
851
911
|
throw new APIError("BAD_REQUEST", {
|
|
@@ -898,18 +958,29 @@ const stripe = (options) => {
|
|
|
898
958
|
userId: user.id
|
|
899
959
|
}
|
|
900
960
|
});
|
|
901
|
-
await ctx2.context.adapter.update(
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
961
|
+
const customer = await ctx2.context.adapter.update(
|
|
962
|
+
{
|
|
963
|
+
model: "user",
|
|
964
|
+
update: {
|
|
965
|
+
stripeCustomerId: stripeCustomer.id
|
|
966
|
+
},
|
|
967
|
+
where: [
|
|
968
|
+
{
|
|
969
|
+
field: "id",
|
|
970
|
+
value: user.id
|
|
971
|
+
}
|
|
972
|
+
]
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
if (!customer) {
|
|
976
|
+
logger.error("#BETTER_AUTH: Failed to create customer");
|
|
977
|
+
} else {
|
|
978
|
+
await options.onCustomerCreate?.({
|
|
979
|
+
customer,
|
|
980
|
+
stripeCustomer,
|
|
981
|
+
user
|
|
982
|
+
});
|
|
983
|
+
}
|
|
913
984
|
}
|
|
914
985
|
}
|
|
915
986
|
}
|
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.
|
|
4
|
+
"version": "1.2.4-beta.10",
|
|
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.
|
|
38
|
+
"better-auth": "^1.2.4-beta.10"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/better-sqlite3": "^7.6.12",
|
package/src/client.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const stripeClient = <
|
|
|
15
15
|
O["subscription"] extends true
|
|
16
16
|
? {
|
|
17
17
|
stripeClient: any;
|
|
18
|
-
stripeWebhookSecret:
|
|
18
|
+
stripeWebhookSecret: string;
|
|
19
19
|
subscription: {
|
|
20
20
|
enabled: true;
|
|
21
21
|
plans: [];
|
|
@@ -23,7 +23,7 @@ export const stripeClient = <
|
|
|
23
23
|
}
|
|
24
24
|
: {
|
|
25
25
|
stripeClient: any;
|
|
26
|
-
stripeWebhookSecret:
|
|
26
|
+
stripeWebhookSecret: string;
|
|
27
27
|
}
|
|
28
28
|
>
|
|
29
29
|
>,
|
package/src/hooks.ts
CHANGED
|
@@ -95,12 +95,12 @@ export async function onSubscriptionUpdated(
|
|
|
95
95
|
const priceId = subscriptionUpdated.items.data[0].price.id;
|
|
96
96
|
const plan = await getPlanByPriceId(options, priceId);
|
|
97
97
|
|
|
98
|
-
const
|
|
98
|
+
const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
|
|
99
99
|
const customerId = subscriptionUpdated.customer?.toString();
|
|
100
100
|
let subscription = await ctx.context.adapter.findOne<Subscription>({
|
|
101
101
|
model: "subscription",
|
|
102
|
-
where:
|
|
103
|
-
? [{ field: "
|
|
102
|
+
where: subscriptionId
|
|
103
|
+
? [{ field: "id", value: subscriptionId }]
|
|
104
104
|
: [{ field: "stripeSubscriptionId", value: subscriptionUpdated.id }],
|
|
105
105
|
});
|
|
106
106
|
if (!subscription) {
|