@better-auth/stripe 1.2.6-beta.6 → 1.2.6
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/index.cjs +94 -2
- package/dist/index.d.cts +69 -1
- package/dist/index.d.mts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.mjs +94 -2
- package/package.json +2 -2
- package/src/hooks.ts +3 -1
- package/src/index.ts +111 -1
- package/src/stripe.test.ts +18 -26
- package/src/types.ts +2 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/stripe@1.2.6
|
|
2
|
+
> @better-auth/stripe@1.2.6 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:
|
|
8
|
+
[log] dist/index.cjs (total size: 36.9 kB, chunk size: 36.9 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:
|
|
12
|
+
[log] dist/index.mjs (total size: 36.6 kB, chunk size: 36.6 kB, exports: stripe)
|
|
13
13
|
|
|
14
14
|
[log] dist/client.mjs (total size: 133 B, chunk size: 133 B, exports: stripeClient)
|
|
15
15
|
|
|
16
|
-
Σ Total dist size (byte size):
|
|
16
|
+
Σ Total dist size (byte size): 199 kB
|
|
17
17
|
[log]
|
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
|
|
|
36
36
|
const priceId = subscription.items.data[0]?.price.id;
|
|
37
37
|
const plan = await getPlanByPriceId(options, priceId);
|
|
38
38
|
if (plan) {
|
|
39
|
-
const referenceId = checkoutSession?.metadata?.referenceId;
|
|
39
|
+
const referenceId = checkoutSession?.client_reference_id || checkoutSession?.metadata?.referenceId;
|
|
40
40
|
const subscriptionId = checkoutSession?.metadata?.subscriptionId;
|
|
41
41
|
const seats = subscription.items.data[0].quantity;
|
|
42
42
|
if (referenceId && subscriptionId) {
|
|
@@ -288,7 +288,9 @@ const STRIPE_ERROR_CODES = {
|
|
|
288
288
|
SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
|
|
289
289
|
ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
|
|
290
290
|
UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
|
|
291
|
-
EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan"
|
|
291
|
+
EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan",
|
|
292
|
+
SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
|
|
293
|
+
SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION: "Subscription is not scheduled for cancellation"
|
|
292
294
|
};
|
|
293
295
|
const getUrl = (ctx, url) => {
|
|
294
296
|
if (url.startsWith("http")) {
|
|
@@ -304,6 +306,14 @@ const stripe = (options) => {
|
|
|
304
306
|
throw new api.APIError("UNAUTHORIZED");
|
|
305
307
|
}
|
|
306
308
|
const referenceId = ctx.body?.referenceId || ctx.query?.referenceId || session.user.id;
|
|
309
|
+
if (ctx.body?.referenceId && !options.subscription?.authorizeReference) {
|
|
310
|
+
betterAuth.logger.error(
|
|
311
|
+
`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`
|
|
312
|
+
);
|
|
313
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
314
|
+
message: "Reference id is not allowed. Read server logs for more details."
|
|
315
|
+
});
|
|
316
|
+
}
|
|
307
317
|
const isAuthorized = ctx.body?.referenceId ? await options.subscription?.authorizeReference?.({
|
|
308
318
|
user: session.user,
|
|
309
319
|
session: session.session,
|
|
@@ -762,6 +772,88 @@ const stripe = (options) => {
|
|
|
762
772
|
};
|
|
763
773
|
}
|
|
764
774
|
),
|
|
775
|
+
restoreSubscription: plugins.createAuthEndpoint(
|
|
776
|
+
"/subscription/restore",
|
|
777
|
+
{
|
|
778
|
+
method: "POST",
|
|
779
|
+
body: zod.z.object({
|
|
780
|
+
referenceId: zod.z.string().optional(),
|
|
781
|
+
subscriptionId: zod.z.string().optional()
|
|
782
|
+
}),
|
|
783
|
+
use: [api.sessionMiddleware, referenceMiddleware("restore-subscription")]
|
|
784
|
+
},
|
|
785
|
+
async (ctx) => {
|
|
786
|
+
const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
|
|
787
|
+
const subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
|
|
788
|
+
model: "subscription",
|
|
789
|
+
where: [
|
|
790
|
+
{
|
|
791
|
+
field: "id",
|
|
792
|
+
value: ctx.body.subscriptionId
|
|
793
|
+
}
|
|
794
|
+
]
|
|
795
|
+
}) : await ctx.context.adapter.findOne({
|
|
796
|
+
model: "subscription",
|
|
797
|
+
where: [
|
|
798
|
+
{
|
|
799
|
+
field: "referenceId",
|
|
800
|
+
value: referenceId
|
|
801
|
+
}
|
|
802
|
+
]
|
|
803
|
+
});
|
|
804
|
+
if (!subscription || !subscription.stripeCustomerId) {
|
|
805
|
+
throw ctx.error("BAD_REQUEST", {
|
|
806
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
if (subscription.status != "active" && subscription.status != "trialing") {
|
|
810
|
+
throw ctx.error("BAD_REQUEST", {
|
|
811
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (!subscription.cancelAtPeriodEnd) {
|
|
815
|
+
throw ctx.error("BAD_REQUEST", {
|
|
816
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
const activeSubscription = await client.subscriptions.list({
|
|
820
|
+
customer: subscription.stripeCustomerId,
|
|
821
|
+
status: "active"
|
|
822
|
+
}).then((res) => res.data[0]);
|
|
823
|
+
if (!activeSubscription) {
|
|
824
|
+
throw ctx.error("BAD_REQUEST", {
|
|
825
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
const newSub = await client.subscriptions.update(
|
|
830
|
+
activeSubscription.id,
|
|
831
|
+
{
|
|
832
|
+
cancel_at_period_end: false
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
await ctx.context.adapter.update({
|
|
836
|
+
model: "subscription",
|
|
837
|
+
update: {
|
|
838
|
+
cancelAtPeriodEnd: false,
|
|
839
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
840
|
+
},
|
|
841
|
+
where: [
|
|
842
|
+
{
|
|
843
|
+
field: "id",
|
|
844
|
+
value: subscription.id
|
|
845
|
+
}
|
|
846
|
+
]
|
|
847
|
+
});
|
|
848
|
+
return ctx.json(newSub);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
ctx.context.logger.error("Error restoring subscription", error);
|
|
851
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
852
|
+
message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
),
|
|
765
857
|
listActiveSubscriptions: plugins.createAuthEndpoint(
|
|
766
858
|
"/subscription/list",
|
|
767
859
|
{
|
package/dist/index.d.cts
CHANGED
|
@@ -297,7 +297,7 @@ interface StripeOptions {
|
|
|
297
297
|
user: User & Record<string, any>;
|
|
298
298
|
session: Session & Record<string, any>;
|
|
299
299
|
referenceId: string;
|
|
300
|
-
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription";
|
|
300
|
+
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription";
|
|
301
301
|
}, request?: Request) => Promise<boolean>;
|
|
302
302
|
/**
|
|
303
303
|
* A callback to run after a user has deleted their subscription
|
|
@@ -786,6 +786,74 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
786
786
|
};
|
|
787
787
|
path: "/subscription/cancel";
|
|
788
788
|
};
|
|
789
|
+
readonly restoreSubscription: {
|
|
790
|
+
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
791
|
+
body: {
|
|
792
|
+
referenceId?: string | undefined;
|
|
793
|
+
subscriptionId?: string | undefined;
|
|
794
|
+
};
|
|
795
|
+
} & {
|
|
796
|
+
method?: "POST" | undefined;
|
|
797
|
+
} & {
|
|
798
|
+
query?: Record<string, any> | undefined;
|
|
799
|
+
} & {
|
|
800
|
+
params?: Record<string, any>;
|
|
801
|
+
} & {
|
|
802
|
+
request?: Request;
|
|
803
|
+
} & {
|
|
804
|
+
headers?: HeadersInit;
|
|
805
|
+
} & {
|
|
806
|
+
asResponse?: boolean;
|
|
807
|
+
returnHeaders?: boolean;
|
|
808
|
+
use?: better_call.Middleware[];
|
|
809
|
+
path?: string;
|
|
810
|
+
} & {
|
|
811
|
+
asResponse?: AsResponse | undefined;
|
|
812
|
+
returnHeaders?: ReturnHeaders | undefined;
|
|
813
|
+
}): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
|
|
814
|
+
headers: Headers;
|
|
815
|
+
response: Stripe.Response<Stripe.Subscription>;
|
|
816
|
+
} : Stripe.Response<Stripe.Subscription>>;
|
|
817
|
+
options: {
|
|
818
|
+
method: "POST";
|
|
819
|
+
body: z.ZodObject<{
|
|
820
|
+
referenceId: z.ZodOptional<z.ZodString>;
|
|
821
|
+
subscriptionId: z.ZodOptional<z.ZodString>;
|
|
822
|
+
}, "strip", z.ZodTypeAny, {
|
|
823
|
+
referenceId?: string | undefined;
|
|
824
|
+
subscriptionId?: string | undefined;
|
|
825
|
+
}, {
|
|
826
|
+
referenceId?: string | undefined;
|
|
827
|
+
subscriptionId?: string | undefined;
|
|
828
|
+
}>;
|
|
829
|
+
use: (((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<{
|
|
830
|
+
session: {
|
|
831
|
+
session: Record<string, any> & {
|
|
832
|
+
id: string;
|
|
833
|
+
createdAt: Date;
|
|
834
|
+
updatedAt: Date;
|
|
835
|
+
userId: string;
|
|
836
|
+
expiresAt: Date;
|
|
837
|
+
token: string;
|
|
838
|
+
ipAddress?: string | null | undefined;
|
|
839
|
+
userAgent?: string | null | undefined;
|
|
840
|
+
};
|
|
841
|
+
user: Record<string, any> & {
|
|
842
|
+
id: string;
|
|
843
|
+
name: string;
|
|
844
|
+
email: string;
|
|
845
|
+
emailVerified: boolean;
|
|
846
|
+
createdAt: Date;
|
|
847
|
+
updatedAt: Date;
|
|
848
|
+
image?: string | null | undefined;
|
|
849
|
+
};
|
|
850
|
+
};
|
|
851
|
+
}>) | ((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<void>))[];
|
|
852
|
+
} & {
|
|
853
|
+
use: any[];
|
|
854
|
+
};
|
|
855
|
+
path: "/subscription/restore";
|
|
856
|
+
};
|
|
789
857
|
readonly listActiveSubscriptions: {
|
|
790
858
|
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0?: ({
|
|
791
859
|
body?: undefined;
|
package/dist/index.d.mts
CHANGED
|
@@ -297,7 +297,7 @@ interface StripeOptions {
|
|
|
297
297
|
user: User & Record<string, any>;
|
|
298
298
|
session: Session & Record<string, any>;
|
|
299
299
|
referenceId: string;
|
|
300
|
-
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription";
|
|
300
|
+
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription";
|
|
301
301
|
}, request?: Request) => Promise<boolean>;
|
|
302
302
|
/**
|
|
303
303
|
* A callback to run after a user has deleted their subscription
|
|
@@ -786,6 +786,74 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
786
786
|
};
|
|
787
787
|
path: "/subscription/cancel";
|
|
788
788
|
};
|
|
789
|
+
readonly restoreSubscription: {
|
|
790
|
+
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
791
|
+
body: {
|
|
792
|
+
referenceId?: string | undefined;
|
|
793
|
+
subscriptionId?: string | undefined;
|
|
794
|
+
};
|
|
795
|
+
} & {
|
|
796
|
+
method?: "POST" | undefined;
|
|
797
|
+
} & {
|
|
798
|
+
query?: Record<string, any> | undefined;
|
|
799
|
+
} & {
|
|
800
|
+
params?: Record<string, any>;
|
|
801
|
+
} & {
|
|
802
|
+
request?: Request;
|
|
803
|
+
} & {
|
|
804
|
+
headers?: HeadersInit;
|
|
805
|
+
} & {
|
|
806
|
+
asResponse?: boolean;
|
|
807
|
+
returnHeaders?: boolean;
|
|
808
|
+
use?: better_call.Middleware[];
|
|
809
|
+
path?: string;
|
|
810
|
+
} & {
|
|
811
|
+
asResponse?: AsResponse | undefined;
|
|
812
|
+
returnHeaders?: ReturnHeaders | undefined;
|
|
813
|
+
}): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
|
|
814
|
+
headers: Headers;
|
|
815
|
+
response: Stripe.Response<Stripe.Subscription>;
|
|
816
|
+
} : Stripe.Response<Stripe.Subscription>>;
|
|
817
|
+
options: {
|
|
818
|
+
method: "POST";
|
|
819
|
+
body: z.ZodObject<{
|
|
820
|
+
referenceId: z.ZodOptional<z.ZodString>;
|
|
821
|
+
subscriptionId: z.ZodOptional<z.ZodString>;
|
|
822
|
+
}, "strip", z.ZodTypeAny, {
|
|
823
|
+
referenceId?: string | undefined;
|
|
824
|
+
subscriptionId?: string | undefined;
|
|
825
|
+
}, {
|
|
826
|
+
referenceId?: string | undefined;
|
|
827
|
+
subscriptionId?: string | undefined;
|
|
828
|
+
}>;
|
|
829
|
+
use: (((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<{
|
|
830
|
+
session: {
|
|
831
|
+
session: Record<string, any> & {
|
|
832
|
+
id: string;
|
|
833
|
+
createdAt: Date;
|
|
834
|
+
updatedAt: Date;
|
|
835
|
+
userId: string;
|
|
836
|
+
expiresAt: Date;
|
|
837
|
+
token: string;
|
|
838
|
+
ipAddress?: string | null | undefined;
|
|
839
|
+
userAgent?: string | null | undefined;
|
|
840
|
+
};
|
|
841
|
+
user: Record<string, any> & {
|
|
842
|
+
id: string;
|
|
843
|
+
name: string;
|
|
844
|
+
email: string;
|
|
845
|
+
emailVerified: boolean;
|
|
846
|
+
createdAt: Date;
|
|
847
|
+
updatedAt: Date;
|
|
848
|
+
image?: string | null | undefined;
|
|
849
|
+
};
|
|
850
|
+
};
|
|
851
|
+
}>) | ((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<void>))[];
|
|
852
|
+
} & {
|
|
853
|
+
use: any[];
|
|
854
|
+
};
|
|
855
|
+
path: "/subscription/restore";
|
|
856
|
+
};
|
|
789
857
|
readonly listActiveSubscriptions: {
|
|
790
858
|
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0?: ({
|
|
791
859
|
body?: undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -297,7 +297,7 @@ interface StripeOptions {
|
|
|
297
297
|
user: User & Record<string, any>;
|
|
298
298
|
session: Session & Record<string, any>;
|
|
299
299
|
referenceId: string;
|
|
300
|
-
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription";
|
|
300
|
+
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription";
|
|
301
301
|
}, request?: Request) => Promise<boolean>;
|
|
302
302
|
/**
|
|
303
303
|
* A callback to run after a user has deleted their subscription
|
|
@@ -786,6 +786,74 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
786
786
|
};
|
|
787
787
|
path: "/subscription/cancel";
|
|
788
788
|
};
|
|
789
|
+
readonly restoreSubscription: {
|
|
790
|
+
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
791
|
+
body: {
|
|
792
|
+
referenceId?: string | undefined;
|
|
793
|
+
subscriptionId?: string | undefined;
|
|
794
|
+
};
|
|
795
|
+
} & {
|
|
796
|
+
method?: "POST" | undefined;
|
|
797
|
+
} & {
|
|
798
|
+
query?: Record<string, any> | undefined;
|
|
799
|
+
} & {
|
|
800
|
+
params?: Record<string, any>;
|
|
801
|
+
} & {
|
|
802
|
+
request?: Request;
|
|
803
|
+
} & {
|
|
804
|
+
headers?: HeadersInit;
|
|
805
|
+
} & {
|
|
806
|
+
asResponse?: boolean;
|
|
807
|
+
returnHeaders?: boolean;
|
|
808
|
+
use?: better_call.Middleware[];
|
|
809
|
+
path?: string;
|
|
810
|
+
} & {
|
|
811
|
+
asResponse?: AsResponse | undefined;
|
|
812
|
+
returnHeaders?: ReturnHeaders | undefined;
|
|
813
|
+
}): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
|
|
814
|
+
headers: Headers;
|
|
815
|
+
response: Stripe.Response<Stripe.Subscription>;
|
|
816
|
+
} : Stripe.Response<Stripe.Subscription>>;
|
|
817
|
+
options: {
|
|
818
|
+
method: "POST";
|
|
819
|
+
body: z.ZodObject<{
|
|
820
|
+
referenceId: z.ZodOptional<z.ZodString>;
|
|
821
|
+
subscriptionId: z.ZodOptional<z.ZodString>;
|
|
822
|
+
}, "strip", z.ZodTypeAny, {
|
|
823
|
+
referenceId?: string | undefined;
|
|
824
|
+
subscriptionId?: string | undefined;
|
|
825
|
+
}, {
|
|
826
|
+
referenceId?: string | undefined;
|
|
827
|
+
subscriptionId?: string | undefined;
|
|
828
|
+
}>;
|
|
829
|
+
use: (((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<{
|
|
830
|
+
session: {
|
|
831
|
+
session: Record<string, any> & {
|
|
832
|
+
id: string;
|
|
833
|
+
createdAt: Date;
|
|
834
|
+
updatedAt: Date;
|
|
835
|
+
userId: string;
|
|
836
|
+
expiresAt: Date;
|
|
837
|
+
token: string;
|
|
838
|
+
ipAddress?: string | null | undefined;
|
|
839
|
+
userAgent?: string | null | undefined;
|
|
840
|
+
};
|
|
841
|
+
user: Record<string, any> & {
|
|
842
|
+
id: string;
|
|
843
|
+
name: string;
|
|
844
|
+
email: string;
|
|
845
|
+
emailVerified: boolean;
|
|
846
|
+
createdAt: Date;
|
|
847
|
+
updatedAt: Date;
|
|
848
|
+
image?: string | null | undefined;
|
|
849
|
+
};
|
|
850
|
+
};
|
|
851
|
+
}>) | ((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<void>))[];
|
|
852
|
+
} & {
|
|
853
|
+
use: any[];
|
|
854
|
+
};
|
|
855
|
+
path: "/subscription/restore";
|
|
856
|
+
};
|
|
789
857
|
readonly listActiveSubscriptions: {
|
|
790
858
|
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0?: ({
|
|
791
859
|
body?: undefined;
|
package/dist/index.mjs
CHANGED
|
@@ -34,7 +34,7 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
|
|
|
34
34
|
const priceId = subscription.items.data[0]?.price.id;
|
|
35
35
|
const plan = await getPlanByPriceId(options, priceId);
|
|
36
36
|
if (plan) {
|
|
37
|
-
const referenceId = checkoutSession?.metadata?.referenceId;
|
|
37
|
+
const referenceId = checkoutSession?.client_reference_id || checkoutSession?.metadata?.referenceId;
|
|
38
38
|
const subscriptionId = checkoutSession?.metadata?.subscriptionId;
|
|
39
39
|
const seats = subscription.items.data[0].quantity;
|
|
40
40
|
if (referenceId && subscriptionId) {
|
|
@@ -286,7 +286,9 @@ const STRIPE_ERROR_CODES = {
|
|
|
286
286
|
SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
|
|
287
287
|
ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
|
|
288
288
|
UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
|
|
289
|
-
EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan"
|
|
289
|
+
EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan",
|
|
290
|
+
SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
|
|
291
|
+
SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION: "Subscription is not scheduled for cancellation"
|
|
290
292
|
};
|
|
291
293
|
const getUrl = (ctx, url) => {
|
|
292
294
|
if (url.startsWith("http")) {
|
|
@@ -302,6 +304,14 @@ const stripe = (options) => {
|
|
|
302
304
|
throw new APIError("UNAUTHORIZED");
|
|
303
305
|
}
|
|
304
306
|
const referenceId = ctx.body?.referenceId || ctx.query?.referenceId || session.user.id;
|
|
307
|
+
if (ctx.body?.referenceId && !options.subscription?.authorizeReference) {
|
|
308
|
+
logger.error(
|
|
309
|
+
`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`
|
|
310
|
+
);
|
|
311
|
+
throw new APIError("BAD_REQUEST", {
|
|
312
|
+
message: "Reference id is not allowed. Read server logs for more details."
|
|
313
|
+
});
|
|
314
|
+
}
|
|
305
315
|
const isAuthorized = ctx.body?.referenceId ? await options.subscription?.authorizeReference?.({
|
|
306
316
|
user: session.user,
|
|
307
317
|
session: session.session,
|
|
@@ -760,6 +770,88 @@ const stripe = (options) => {
|
|
|
760
770
|
};
|
|
761
771
|
}
|
|
762
772
|
),
|
|
773
|
+
restoreSubscription: createAuthEndpoint(
|
|
774
|
+
"/subscription/restore",
|
|
775
|
+
{
|
|
776
|
+
method: "POST",
|
|
777
|
+
body: z.object({
|
|
778
|
+
referenceId: z.string().optional(),
|
|
779
|
+
subscriptionId: z.string().optional()
|
|
780
|
+
}),
|
|
781
|
+
use: [sessionMiddleware, referenceMiddleware("restore-subscription")]
|
|
782
|
+
},
|
|
783
|
+
async (ctx) => {
|
|
784
|
+
const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
|
|
785
|
+
const subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
|
|
786
|
+
model: "subscription",
|
|
787
|
+
where: [
|
|
788
|
+
{
|
|
789
|
+
field: "id",
|
|
790
|
+
value: ctx.body.subscriptionId
|
|
791
|
+
}
|
|
792
|
+
]
|
|
793
|
+
}) : await ctx.context.adapter.findOne({
|
|
794
|
+
model: "subscription",
|
|
795
|
+
where: [
|
|
796
|
+
{
|
|
797
|
+
field: "referenceId",
|
|
798
|
+
value: referenceId
|
|
799
|
+
}
|
|
800
|
+
]
|
|
801
|
+
});
|
|
802
|
+
if (!subscription || !subscription.stripeCustomerId) {
|
|
803
|
+
throw ctx.error("BAD_REQUEST", {
|
|
804
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
if (subscription.status != "active" && subscription.status != "trialing") {
|
|
808
|
+
throw ctx.error("BAD_REQUEST", {
|
|
809
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
if (!subscription.cancelAtPeriodEnd) {
|
|
813
|
+
throw ctx.error("BAD_REQUEST", {
|
|
814
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
const activeSubscription = await client.subscriptions.list({
|
|
818
|
+
customer: subscription.stripeCustomerId,
|
|
819
|
+
status: "active"
|
|
820
|
+
}).then((res) => res.data[0]);
|
|
821
|
+
if (!activeSubscription) {
|
|
822
|
+
throw ctx.error("BAD_REQUEST", {
|
|
823
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const newSub = await client.subscriptions.update(
|
|
828
|
+
activeSubscription.id,
|
|
829
|
+
{
|
|
830
|
+
cancel_at_period_end: false
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
await ctx.context.adapter.update({
|
|
834
|
+
model: "subscription",
|
|
835
|
+
update: {
|
|
836
|
+
cancelAtPeriodEnd: false,
|
|
837
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
838
|
+
},
|
|
839
|
+
where: [
|
|
840
|
+
{
|
|
841
|
+
field: "id",
|
|
842
|
+
value: subscription.id
|
|
843
|
+
}
|
|
844
|
+
]
|
|
845
|
+
});
|
|
846
|
+
return ctx.json(newSub);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
ctx.context.logger.error("Error restoring subscription", error);
|
|
849
|
+
throw new APIError("BAD_REQUEST", {
|
|
850
|
+
message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
),
|
|
763
855
|
listActiveSubscriptions: createAuthEndpoint(
|
|
764
856
|
"/subscription/list",
|
|
765
857
|
{
|
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.6
|
|
4
|
+
"version": "1.2.6",
|
|
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.6
|
|
38
|
+
"better-auth": "^1.2.6"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/better-sqlite3": "^7.6.12",
|
package/src/hooks.ts
CHANGED
|
@@ -20,7 +20,9 @@ export async function onCheckoutSessionCompleted(
|
|
|
20
20
|
const priceId = subscription.items.data[0]?.price.id;
|
|
21
21
|
const plan = await getPlanByPriceId(options, priceId as string);
|
|
22
22
|
if (plan) {
|
|
23
|
-
const referenceId =
|
|
23
|
+
const referenceId =
|
|
24
|
+
checkoutSession?.client_reference_id ||
|
|
25
|
+
checkoutSession?.metadata?.referenceId;
|
|
24
26
|
const subscriptionId = checkoutSession?.metadata?.subscriptionId;
|
|
25
27
|
const seats = subscription.items.data[0].quantity;
|
|
26
28
|
if (referenceId && subscriptionId) {
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,9 @@ const STRIPE_ERROR_CODES = {
|
|
|
35
35
|
FAILED_TO_FETCH_PLANS: "Failed to fetch plans",
|
|
36
36
|
EMAIL_VERIFICATION_REQUIRED:
|
|
37
37
|
"Email verification is required before you can subscribe to a plan",
|
|
38
|
+
SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
|
|
39
|
+
SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION:
|
|
40
|
+
"Subscription is not scheduled for cancellation",
|
|
38
41
|
} as const;
|
|
39
42
|
|
|
40
43
|
const getUrl = (ctx: GenericEndpointContext, url: string) => {
|
|
@@ -53,7 +56,8 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
53
56
|
action:
|
|
54
57
|
| "upgrade-subscription"
|
|
55
58
|
| "list-subscription"
|
|
56
|
-
| "cancel-subscription"
|
|
59
|
+
| "cancel-subscription"
|
|
60
|
+
| "restore-subscription",
|
|
57
61
|
) =>
|
|
58
62
|
createAuthMiddleware(async (ctx) => {
|
|
59
63
|
const session = ctx.context.session;
|
|
@@ -62,6 +66,16 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
62
66
|
}
|
|
63
67
|
const referenceId =
|
|
64
68
|
ctx.body?.referenceId || ctx.query?.referenceId || session.user.id;
|
|
69
|
+
|
|
70
|
+
if (ctx.body?.referenceId && !options.subscription?.authorizeReference) {
|
|
71
|
+
logger.error(
|
|
72
|
+
`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`,
|
|
73
|
+
);
|
|
74
|
+
throw new APIError("BAD_REQUEST", {
|
|
75
|
+
message:
|
|
76
|
+
"Reference id is not allowed. Read server logs for more details.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
65
79
|
const isAuthorized = ctx.body?.referenceId
|
|
66
80
|
? await options.subscription?.authorizeReference?.({
|
|
67
81
|
user: session.user,
|
|
@@ -611,6 +625,102 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
611
625
|
};
|
|
612
626
|
},
|
|
613
627
|
),
|
|
628
|
+
restoreSubscription: createAuthEndpoint(
|
|
629
|
+
"/subscription/restore",
|
|
630
|
+
{
|
|
631
|
+
method: "POST",
|
|
632
|
+
body: z.object({
|
|
633
|
+
referenceId: z.string().optional(),
|
|
634
|
+
subscriptionId: z.string().optional(),
|
|
635
|
+
}),
|
|
636
|
+
use: [sessionMiddleware, referenceMiddleware("restore-subscription")],
|
|
637
|
+
},
|
|
638
|
+
async (ctx) => {
|
|
639
|
+
const referenceId =
|
|
640
|
+
ctx.body?.referenceId || ctx.context.session.user.id;
|
|
641
|
+
|
|
642
|
+
const subscription = ctx.body.subscriptionId
|
|
643
|
+
? await ctx.context.adapter.findOne<Subscription>({
|
|
644
|
+
model: "subscription",
|
|
645
|
+
where: [
|
|
646
|
+
{
|
|
647
|
+
field: "id",
|
|
648
|
+
value: ctx.body.subscriptionId,
|
|
649
|
+
},
|
|
650
|
+
],
|
|
651
|
+
})
|
|
652
|
+
: await ctx.context.adapter.findOne<Subscription>({
|
|
653
|
+
model: "subscription",
|
|
654
|
+
where: [
|
|
655
|
+
{
|
|
656
|
+
field: "referenceId",
|
|
657
|
+
value: referenceId,
|
|
658
|
+
},
|
|
659
|
+
],
|
|
660
|
+
});
|
|
661
|
+
if (!subscription || !subscription.stripeCustomerId) {
|
|
662
|
+
throw ctx.error("BAD_REQUEST", {
|
|
663
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (
|
|
667
|
+
subscription.status != "active" &&
|
|
668
|
+
subscription.status != "trialing"
|
|
669
|
+
) {
|
|
670
|
+
throw ctx.error("BAD_REQUEST", {
|
|
671
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
if (!subscription.cancelAtPeriodEnd) {
|
|
675
|
+
throw ctx.error("BAD_REQUEST", {
|
|
676
|
+
message:
|
|
677
|
+
STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const activeSubscription = await client.subscriptions
|
|
682
|
+
.list({
|
|
683
|
+
customer: subscription.stripeCustomerId,
|
|
684
|
+
status: "active",
|
|
685
|
+
})
|
|
686
|
+
.then((res) => res.data[0]);
|
|
687
|
+
if (!activeSubscription) {
|
|
688
|
+
throw ctx.error("BAD_REQUEST", {
|
|
689
|
+
message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
const newSub = await client.subscriptions.update(
|
|
695
|
+
activeSubscription.id,
|
|
696
|
+
{
|
|
697
|
+
cancel_at_period_end: false,
|
|
698
|
+
},
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
await ctx.context.adapter.update({
|
|
702
|
+
model: "subscription",
|
|
703
|
+
update: {
|
|
704
|
+
cancelAtPeriodEnd: false,
|
|
705
|
+
updatedAt: new Date(),
|
|
706
|
+
},
|
|
707
|
+
where: [
|
|
708
|
+
{
|
|
709
|
+
field: "id",
|
|
710
|
+
value: subscription.id,
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
return ctx.json(newSub);
|
|
716
|
+
} catch (error) {
|
|
717
|
+
ctx.context.logger.error("Error restoring subscription", error);
|
|
718
|
+
throw new APIError("BAD_REQUEST", {
|
|
719
|
+
message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
),
|
|
614
724
|
listActiveSubscriptions: createAuthEndpoint(
|
|
615
725
|
"/subscription/list",
|
|
616
726
|
{
|
package/src/stripe.test.ts
CHANGED
|
@@ -32,6 +32,7 @@ describe("stripe", async () => {
|
|
|
32
32
|
subscriptions: {
|
|
33
33
|
retrieve: vi.fn(),
|
|
34
34
|
list: vi.fn().mockResolvedValue({ data: [] }),
|
|
35
|
+
update: vi.fn(),
|
|
35
36
|
},
|
|
36
37
|
webhooks: {
|
|
37
38
|
constructEvent: vi.fn(),
|
|
@@ -243,19 +244,15 @@ describe("stripe", async () => {
|
|
|
243
244
|
});
|
|
244
245
|
|
|
245
246
|
it("should handle subscription webhook events", async () => {
|
|
246
|
-
const
|
|
247
|
-
const testReferenceId = "user_123";
|
|
248
|
-
await ctx.adapter.create({
|
|
247
|
+
const { id: testReferenceId } = await ctx.adapter.create({
|
|
249
248
|
model: "user",
|
|
250
249
|
data: {
|
|
251
|
-
id: testReferenceId,
|
|
252
250
|
email: "test@email.com",
|
|
253
251
|
},
|
|
254
252
|
});
|
|
255
|
-
await ctx.adapter.create({
|
|
253
|
+
const { id: testSubscriptionId } = await ctx.adapter.create({
|
|
256
254
|
model: "subscription",
|
|
257
255
|
data: {
|
|
258
|
-
id: testSubscriptionId,
|
|
259
256
|
referenceId: testReferenceId,
|
|
260
257
|
stripeCustomerId: "cus_mock123",
|
|
261
258
|
status: "active",
|
|
@@ -353,22 +350,19 @@ describe("stripe", async () => {
|
|
|
353
350
|
});
|
|
354
351
|
});
|
|
355
352
|
|
|
353
|
+
const { id: userId } = await ctx.adapter.create({
|
|
354
|
+
model: "user",
|
|
355
|
+
data: {
|
|
356
|
+
email: "delete-test@email.com",
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
|
|
356
360
|
it("should handle subscription deletion webhook", async () => {
|
|
357
|
-
const userId = "test_user";
|
|
358
361
|
const subId = "test_sub_delete";
|
|
359
362
|
|
|
360
|
-
await ctx.adapter.create({
|
|
361
|
-
model: "user",
|
|
362
|
-
data: {
|
|
363
|
-
id: userId,
|
|
364
|
-
email: "delete-test@email.com",
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
|
|
368
363
|
await ctx.adapter.create({
|
|
369
364
|
model: "subscription",
|
|
370
365
|
data: {
|
|
371
|
-
id: subId,
|
|
372
366
|
referenceId: userId,
|
|
373
367
|
stripeCustomerId: "cus_delete_test",
|
|
374
368
|
status: "active",
|
|
@@ -501,7 +495,6 @@ describe("stripe", async () => {
|
|
|
501
495
|
};
|
|
502
496
|
|
|
503
497
|
const mockSubscription = {
|
|
504
|
-
id: "sub_123",
|
|
505
498
|
status: "active",
|
|
506
499
|
items: {
|
|
507
500
|
data: [{ price: { id: process.env.STRIPE_PRICE_ID_1 } }],
|
|
@@ -532,11 +525,10 @@ describe("stripe", async () => {
|
|
|
532
525
|
plugins: [stripe(eventTestOptions)],
|
|
533
526
|
});
|
|
534
527
|
|
|
535
|
-
await ctx.adapter.create({
|
|
528
|
+
const { id: testSubscriptionId } = await ctx.adapter.create({
|
|
536
529
|
model: "subscription",
|
|
537
530
|
data: {
|
|
538
|
-
|
|
539
|
-
referenceId: "user_123",
|
|
531
|
+
referenceId: userId,
|
|
540
532
|
stripeCustomerId: "cus_123",
|
|
541
533
|
stripeSubscriptionId: "sub_123",
|
|
542
534
|
status: "incomplete",
|
|
@@ -570,7 +562,7 @@ describe("stripe", async () => {
|
|
|
570
562
|
type: "customer.subscription.updated",
|
|
571
563
|
data: {
|
|
572
564
|
object: {
|
|
573
|
-
id:
|
|
565
|
+
id: testSubscriptionId,
|
|
574
566
|
customer: "cus_123",
|
|
575
567
|
status: "active",
|
|
576
568
|
items: {
|
|
@@ -608,7 +600,7 @@ describe("stripe", async () => {
|
|
|
608
600
|
type: "customer.subscription.updated",
|
|
609
601
|
data: {
|
|
610
602
|
object: {
|
|
611
|
-
id:
|
|
603
|
+
id: testSubscriptionId,
|
|
612
604
|
customer: "cus_123",
|
|
613
605
|
status: "active",
|
|
614
606
|
cancel_at_period_end: true,
|
|
@@ -644,7 +636,7 @@ describe("stripe", async () => {
|
|
|
644
636
|
type: "customer.subscription.updated",
|
|
645
637
|
data: {
|
|
646
638
|
object: {
|
|
647
|
-
id:
|
|
639
|
+
id: testSubscriptionId,
|
|
648
640
|
customer: "cus_123",
|
|
649
641
|
status: "active",
|
|
650
642
|
cancel_at_period_end: true,
|
|
@@ -679,12 +671,12 @@ describe("stripe", async () => {
|
|
|
679
671
|
type: "customer.subscription.deleted",
|
|
680
672
|
data: {
|
|
681
673
|
object: {
|
|
682
|
-
id:
|
|
674
|
+
id: testSubscriptionId,
|
|
683
675
|
customer: "cus_123",
|
|
684
676
|
status: "canceled",
|
|
685
677
|
metadata: {
|
|
686
|
-
referenceId:
|
|
687
|
-
subscriptionId:
|
|
678
|
+
referenceId: userId,
|
|
679
|
+
subscriptionId: testSubscriptionId,
|
|
688
680
|
},
|
|
689
681
|
},
|
|
690
682
|
},
|
package/src/types.ts
CHANGED