@better-auth/stripe 1.3.5-beta.3 → 1.3.5-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 +4 -4
- package/dist/index.cjs +59 -0
- 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 +60 -1
- package/package.json +2 -2
- package/src/index.ts +69 -1
- package/src/stripe.test.ts +36 -0
- package/src/types.ts +2 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/stripe@1.3.5-beta.
|
|
2
|
+
> @better-auth/stripe@1.3.5-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:
|
|
8
|
+
[log] dist/index.cjs (total size: 43.3 kB, chunk size: 43.3 kB, exports: stripe)
|
|
9
9
|
|
|
10
10
|
[log] dist/client.cjs (total size: 224 B, chunk size: 224 B, exports: stripeClient)
|
|
11
11
|
|
|
12
|
-
[log] dist/index.mjs (total size:
|
|
12
|
+
[log] dist/index.mjs (total size: 42.5 kB, chunk size: 42.5 kB, exports: stripe)
|
|
13
13
|
|
|
14
14
|
[log] dist/client.mjs (total size: 197 B, chunk size: 197 B, exports: stripeClient)
|
|
15
15
|
|
|
16
|
-
Σ Total dist size (byte size):
|
|
16
|
+
Σ Total dist size (byte size): 216 kB
|
|
17
17
|
[log]
|
package/dist/index.cjs
CHANGED
|
@@ -1123,6 +1123,65 @@ const stripe = (options) => {
|
|
|
1123
1123
|
}
|
|
1124
1124
|
throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1125
1125
|
}
|
|
1126
|
+
),
|
|
1127
|
+
createBillingPortal: plugins.createAuthEndpoint(
|
|
1128
|
+
"/subscription/billing-portal",
|
|
1129
|
+
{
|
|
1130
|
+
method: "POST",
|
|
1131
|
+
body: z__namespace.object({
|
|
1132
|
+
referenceId: z__namespace.string().optional(),
|
|
1133
|
+
returnUrl: z__namespace.string().default("/")
|
|
1134
|
+
}),
|
|
1135
|
+
use: [
|
|
1136
|
+
api.sessionMiddleware,
|
|
1137
|
+
api.originCheck((ctx) => ctx.body.returnUrl),
|
|
1138
|
+
referenceMiddleware("billing-portal")
|
|
1139
|
+
]
|
|
1140
|
+
},
|
|
1141
|
+
async (ctx) => {
|
|
1142
|
+
const { user } = ctx.context.session;
|
|
1143
|
+
const referenceId = ctx.body.referenceId || user.id;
|
|
1144
|
+
let customerId = user.stripeCustomerId;
|
|
1145
|
+
if (!customerId) {
|
|
1146
|
+
const subscription = await ctx.context.adapter.findMany({
|
|
1147
|
+
model: "subscription",
|
|
1148
|
+
where: [
|
|
1149
|
+
{
|
|
1150
|
+
field: "referenceId",
|
|
1151
|
+
value: referenceId
|
|
1152
|
+
}
|
|
1153
|
+
]
|
|
1154
|
+
}).then(
|
|
1155
|
+
(subs) => subs.find(
|
|
1156
|
+
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
1157
|
+
)
|
|
1158
|
+
);
|
|
1159
|
+
customerId = subscription?.stripeCustomerId;
|
|
1160
|
+
}
|
|
1161
|
+
if (!customerId) {
|
|
1162
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
1163
|
+
message: "No Stripe customer found for this user"
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
try {
|
|
1167
|
+
const { url } = await client.billingPortal.sessions.create({
|
|
1168
|
+
customer: customerId,
|
|
1169
|
+
return_url: getUrl(ctx, ctx.body.returnUrl)
|
|
1170
|
+
});
|
|
1171
|
+
return ctx.json({
|
|
1172
|
+
url,
|
|
1173
|
+
redirect: true
|
|
1174
|
+
});
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
ctx.context.logger.error(
|
|
1177
|
+
"Error creating billing portal session",
|
|
1178
|
+
error
|
|
1179
|
+
);
|
|
1180
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
1181
|
+
message: error.message
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1126
1185
|
)
|
|
1127
1186
|
};
|
|
1128
1187
|
return {
|
package/dist/index.d.cts
CHANGED
|
@@ -296,7 +296,7 @@ interface StripeOptions {
|
|
|
296
296
|
user: User & Record<string, any>;
|
|
297
297
|
session: Session & Record<string, any>;
|
|
298
298
|
referenceId: string;
|
|
299
|
-
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription";
|
|
299
|
+
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription" | "billing-portal";
|
|
300
300
|
}, ctx: GenericEndpointContext) => Promise<boolean>;
|
|
301
301
|
/**
|
|
302
302
|
* A callback to run after a user has deleted their subscription
|
|
@@ -957,6 +957,74 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
957
957
|
};
|
|
958
958
|
path: "/subscription/success";
|
|
959
959
|
};
|
|
960
|
+
readonly createBillingPortal: {
|
|
961
|
+
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
962
|
+
body: {
|
|
963
|
+
referenceId?: string | undefined;
|
|
964
|
+
returnUrl?: string | undefined;
|
|
965
|
+
};
|
|
966
|
+
} & {
|
|
967
|
+
method?: "POST" | undefined;
|
|
968
|
+
} & {
|
|
969
|
+
query?: Record<string, any> | undefined;
|
|
970
|
+
} & {
|
|
971
|
+
params?: Record<string, any>;
|
|
972
|
+
} & {
|
|
973
|
+
request?: Request;
|
|
974
|
+
} & {
|
|
975
|
+
headers?: HeadersInit;
|
|
976
|
+
} & {
|
|
977
|
+
asResponse?: boolean;
|
|
978
|
+
returnHeaders?: boolean;
|
|
979
|
+
use?: better_call.Middleware[];
|
|
980
|
+
path?: string;
|
|
981
|
+
} & {
|
|
982
|
+
asResponse?: AsResponse | undefined;
|
|
983
|
+
returnHeaders?: ReturnHeaders | undefined;
|
|
984
|
+
}): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
|
|
985
|
+
headers: Headers;
|
|
986
|
+
response: {
|
|
987
|
+
url: string;
|
|
988
|
+
redirect: boolean;
|
|
989
|
+
};
|
|
990
|
+
} : {
|
|
991
|
+
url: string;
|
|
992
|
+
redirect: boolean;
|
|
993
|
+
}>;
|
|
994
|
+
options: {
|
|
995
|
+
method: "POST";
|
|
996
|
+
body: z.ZodObject<{
|
|
997
|
+
referenceId: z.ZodOptional<z.ZodString>;
|
|
998
|
+
returnUrl: z.ZodDefault<z.ZodString>;
|
|
999
|
+
}, z.core.$strip>;
|
|
1000
|
+
use: (((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<{
|
|
1001
|
+
session: {
|
|
1002
|
+
session: Record<string, any> & {
|
|
1003
|
+
id: string;
|
|
1004
|
+
userId: string;
|
|
1005
|
+
expiresAt: Date;
|
|
1006
|
+
createdAt: Date;
|
|
1007
|
+
updatedAt: Date;
|
|
1008
|
+
token: string;
|
|
1009
|
+
ipAddress?: string | null | undefined;
|
|
1010
|
+
userAgent?: string | null | undefined;
|
|
1011
|
+
};
|
|
1012
|
+
user: Record<string, any> & {
|
|
1013
|
+
id: string;
|
|
1014
|
+
email: string;
|
|
1015
|
+
emailVerified: boolean;
|
|
1016
|
+
name: string;
|
|
1017
|
+
createdAt: Date;
|
|
1018
|
+
updatedAt: Date;
|
|
1019
|
+
image?: string | null | undefined;
|
|
1020
|
+
};
|
|
1021
|
+
};
|
|
1022
|
+
}>) | ((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<void>))[];
|
|
1023
|
+
} & {
|
|
1024
|
+
use: any[];
|
|
1025
|
+
};
|
|
1026
|
+
path: "/subscription/billing-portal";
|
|
1027
|
+
};
|
|
960
1028
|
} : {});
|
|
961
1029
|
init(ctx: better_auth.AuthContext): {
|
|
962
1030
|
options: {
|
package/dist/index.d.mts
CHANGED
|
@@ -296,7 +296,7 @@ interface StripeOptions {
|
|
|
296
296
|
user: User & Record<string, any>;
|
|
297
297
|
session: Session & Record<string, any>;
|
|
298
298
|
referenceId: string;
|
|
299
|
-
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription";
|
|
299
|
+
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription" | "billing-portal";
|
|
300
300
|
}, ctx: GenericEndpointContext) => Promise<boolean>;
|
|
301
301
|
/**
|
|
302
302
|
* A callback to run after a user has deleted their subscription
|
|
@@ -957,6 +957,74 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
957
957
|
};
|
|
958
958
|
path: "/subscription/success";
|
|
959
959
|
};
|
|
960
|
+
readonly createBillingPortal: {
|
|
961
|
+
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
962
|
+
body: {
|
|
963
|
+
referenceId?: string | undefined;
|
|
964
|
+
returnUrl?: string | undefined;
|
|
965
|
+
};
|
|
966
|
+
} & {
|
|
967
|
+
method?: "POST" | undefined;
|
|
968
|
+
} & {
|
|
969
|
+
query?: Record<string, any> | undefined;
|
|
970
|
+
} & {
|
|
971
|
+
params?: Record<string, any>;
|
|
972
|
+
} & {
|
|
973
|
+
request?: Request;
|
|
974
|
+
} & {
|
|
975
|
+
headers?: HeadersInit;
|
|
976
|
+
} & {
|
|
977
|
+
asResponse?: boolean;
|
|
978
|
+
returnHeaders?: boolean;
|
|
979
|
+
use?: better_call.Middleware[];
|
|
980
|
+
path?: string;
|
|
981
|
+
} & {
|
|
982
|
+
asResponse?: AsResponse | undefined;
|
|
983
|
+
returnHeaders?: ReturnHeaders | undefined;
|
|
984
|
+
}): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
|
|
985
|
+
headers: Headers;
|
|
986
|
+
response: {
|
|
987
|
+
url: string;
|
|
988
|
+
redirect: boolean;
|
|
989
|
+
};
|
|
990
|
+
} : {
|
|
991
|
+
url: string;
|
|
992
|
+
redirect: boolean;
|
|
993
|
+
}>;
|
|
994
|
+
options: {
|
|
995
|
+
method: "POST";
|
|
996
|
+
body: z.ZodObject<{
|
|
997
|
+
referenceId: z.ZodOptional<z.ZodString>;
|
|
998
|
+
returnUrl: z.ZodDefault<z.ZodString>;
|
|
999
|
+
}, z.core.$strip>;
|
|
1000
|
+
use: (((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<{
|
|
1001
|
+
session: {
|
|
1002
|
+
session: Record<string, any> & {
|
|
1003
|
+
id: string;
|
|
1004
|
+
userId: string;
|
|
1005
|
+
expiresAt: Date;
|
|
1006
|
+
createdAt: Date;
|
|
1007
|
+
updatedAt: Date;
|
|
1008
|
+
token: string;
|
|
1009
|
+
ipAddress?: string | null | undefined;
|
|
1010
|
+
userAgent?: string | null | undefined;
|
|
1011
|
+
};
|
|
1012
|
+
user: Record<string, any> & {
|
|
1013
|
+
id: string;
|
|
1014
|
+
email: string;
|
|
1015
|
+
emailVerified: boolean;
|
|
1016
|
+
name: string;
|
|
1017
|
+
createdAt: Date;
|
|
1018
|
+
updatedAt: Date;
|
|
1019
|
+
image?: string | null | undefined;
|
|
1020
|
+
};
|
|
1021
|
+
};
|
|
1022
|
+
}>) | ((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<void>))[];
|
|
1023
|
+
} & {
|
|
1024
|
+
use: any[];
|
|
1025
|
+
};
|
|
1026
|
+
path: "/subscription/billing-portal";
|
|
1027
|
+
};
|
|
960
1028
|
} : {});
|
|
961
1029
|
init(ctx: better_auth.AuthContext): {
|
|
962
1030
|
options: {
|
package/dist/index.d.ts
CHANGED
|
@@ -296,7 +296,7 @@ interface StripeOptions {
|
|
|
296
296
|
user: User & Record<string, any>;
|
|
297
297
|
session: Session & Record<string, any>;
|
|
298
298
|
referenceId: string;
|
|
299
|
-
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription";
|
|
299
|
+
action: "upgrade-subscription" | "list-subscription" | "cancel-subscription" | "restore-subscription" | "billing-portal";
|
|
300
300
|
}, ctx: GenericEndpointContext) => Promise<boolean>;
|
|
301
301
|
/**
|
|
302
302
|
* A callback to run after a user has deleted their subscription
|
|
@@ -957,6 +957,74 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
957
957
|
};
|
|
958
958
|
path: "/subscription/success";
|
|
959
959
|
};
|
|
960
|
+
readonly createBillingPortal: {
|
|
961
|
+
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
962
|
+
body: {
|
|
963
|
+
referenceId?: string | undefined;
|
|
964
|
+
returnUrl?: string | undefined;
|
|
965
|
+
};
|
|
966
|
+
} & {
|
|
967
|
+
method?: "POST" | undefined;
|
|
968
|
+
} & {
|
|
969
|
+
query?: Record<string, any> | undefined;
|
|
970
|
+
} & {
|
|
971
|
+
params?: Record<string, any>;
|
|
972
|
+
} & {
|
|
973
|
+
request?: Request;
|
|
974
|
+
} & {
|
|
975
|
+
headers?: HeadersInit;
|
|
976
|
+
} & {
|
|
977
|
+
asResponse?: boolean;
|
|
978
|
+
returnHeaders?: boolean;
|
|
979
|
+
use?: better_call.Middleware[];
|
|
980
|
+
path?: string;
|
|
981
|
+
} & {
|
|
982
|
+
asResponse?: AsResponse | undefined;
|
|
983
|
+
returnHeaders?: ReturnHeaders | undefined;
|
|
984
|
+
}): Promise<[AsResponse] extends [true] ? Response : [ReturnHeaders] extends [true] ? {
|
|
985
|
+
headers: Headers;
|
|
986
|
+
response: {
|
|
987
|
+
url: string;
|
|
988
|
+
redirect: boolean;
|
|
989
|
+
};
|
|
990
|
+
} : {
|
|
991
|
+
url: string;
|
|
992
|
+
redirect: boolean;
|
|
993
|
+
}>;
|
|
994
|
+
options: {
|
|
995
|
+
method: "POST";
|
|
996
|
+
body: z.ZodObject<{
|
|
997
|
+
referenceId: z.ZodOptional<z.ZodString>;
|
|
998
|
+
returnUrl: z.ZodDefault<z.ZodString>;
|
|
999
|
+
}, z.core.$strip>;
|
|
1000
|
+
use: (((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<{
|
|
1001
|
+
session: {
|
|
1002
|
+
session: Record<string, any> & {
|
|
1003
|
+
id: string;
|
|
1004
|
+
userId: string;
|
|
1005
|
+
expiresAt: Date;
|
|
1006
|
+
createdAt: Date;
|
|
1007
|
+
updatedAt: Date;
|
|
1008
|
+
token: string;
|
|
1009
|
+
ipAddress?: string | null | undefined;
|
|
1010
|
+
userAgent?: string | null | undefined;
|
|
1011
|
+
};
|
|
1012
|
+
user: Record<string, any> & {
|
|
1013
|
+
id: string;
|
|
1014
|
+
email: string;
|
|
1015
|
+
emailVerified: boolean;
|
|
1016
|
+
name: string;
|
|
1017
|
+
createdAt: Date;
|
|
1018
|
+
updatedAt: Date;
|
|
1019
|
+
image?: string | null | undefined;
|
|
1020
|
+
};
|
|
1021
|
+
};
|
|
1022
|
+
}>) | ((inputContext: better_call.MiddlewareInputContext<better_call.MiddlewareOptions>) => Promise<void>))[];
|
|
1023
|
+
} & {
|
|
1024
|
+
use: any[];
|
|
1025
|
+
};
|
|
1026
|
+
path: "/subscription/billing-portal";
|
|
1027
|
+
};
|
|
960
1028
|
} : {});
|
|
961
1029
|
init(ctx: better_auth.AuthContext): {
|
|
962
1030
|
options: {
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from 'better-auth';
|
|
2
2
|
import { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins';
|
|
3
3
|
import * as z from 'zod/v4';
|
|
4
|
-
import {
|
|
4
|
+
import { sessionMiddleware, originCheck, APIError, getSessionFromCtx } from 'better-auth/api';
|
|
5
5
|
import { mergeSchema } from 'better-auth/db';
|
|
6
6
|
|
|
7
7
|
async function getPlans(options) {
|
|
@@ -1107,6 +1107,65 @@ const stripe = (options) => {
|
|
|
1107
1107
|
}
|
|
1108
1108
|
throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1109
1109
|
}
|
|
1110
|
+
),
|
|
1111
|
+
createBillingPortal: createAuthEndpoint(
|
|
1112
|
+
"/subscription/billing-portal",
|
|
1113
|
+
{
|
|
1114
|
+
method: "POST",
|
|
1115
|
+
body: z.object({
|
|
1116
|
+
referenceId: z.string().optional(),
|
|
1117
|
+
returnUrl: z.string().default("/")
|
|
1118
|
+
}),
|
|
1119
|
+
use: [
|
|
1120
|
+
sessionMiddleware,
|
|
1121
|
+
originCheck((ctx) => ctx.body.returnUrl),
|
|
1122
|
+
referenceMiddleware("billing-portal")
|
|
1123
|
+
]
|
|
1124
|
+
},
|
|
1125
|
+
async (ctx) => {
|
|
1126
|
+
const { user } = ctx.context.session;
|
|
1127
|
+
const referenceId = ctx.body.referenceId || user.id;
|
|
1128
|
+
let customerId = user.stripeCustomerId;
|
|
1129
|
+
if (!customerId) {
|
|
1130
|
+
const subscription = await ctx.context.adapter.findMany({
|
|
1131
|
+
model: "subscription",
|
|
1132
|
+
where: [
|
|
1133
|
+
{
|
|
1134
|
+
field: "referenceId",
|
|
1135
|
+
value: referenceId
|
|
1136
|
+
}
|
|
1137
|
+
]
|
|
1138
|
+
}).then(
|
|
1139
|
+
(subs) => subs.find(
|
|
1140
|
+
(sub) => sub.status === "active" || sub.status === "trialing"
|
|
1141
|
+
)
|
|
1142
|
+
);
|
|
1143
|
+
customerId = subscription?.stripeCustomerId;
|
|
1144
|
+
}
|
|
1145
|
+
if (!customerId) {
|
|
1146
|
+
throw new APIError("BAD_REQUEST", {
|
|
1147
|
+
message: "No Stripe customer found for this user"
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
try {
|
|
1151
|
+
const { url } = await client.billingPortal.sessions.create({
|
|
1152
|
+
customer: customerId,
|
|
1153
|
+
return_url: getUrl(ctx, ctx.body.returnUrl)
|
|
1154
|
+
});
|
|
1155
|
+
return ctx.json({
|
|
1156
|
+
url,
|
|
1157
|
+
redirect: true
|
|
1158
|
+
});
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
ctx.context.logger.error(
|
|
1161
|
+
"Error creating billing portal session",
|
|
1162
|
+
error
|
|
1163
|
+
);
|
|
1164
|
+
throw new APIError("BAD_REQUEST", {
|
|
1165
|
+
message: error.message
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1110
1169
|
)
|
|
1111
1170
|
};
|
|
1112
1171
|
return {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/stripe",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.3.5-beta.
|
|
4
|
+
"version": "1.3.5-beta.5",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"better-auth": "^1.3.5-beta.
|
|
40
|
+
"better-auth": "^1.3.5-beta.5"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"zod": "^3.25.0 || ^4.0.0"
|
package/src/index.ts
CHANGED
|
@@ -69,7 +69,8 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
69
69
|
| "upgrade-subscription"
|
|
70
70
|
| "list-subscription"
|
|
71
71
|
| "cancel-subscription"
|
|
72
|
-
| "restore-subscription"
|
|
72
|
+
| "restore-subscription"
|
|
73
|
+
| "billing-portal",
|
|
73
74
|
) =>
|
|
74
75
|
createAuthMiddleware(async (ctx) => {
|
|
75
76
|
const session = ctx.context.session;
|
|
@@ -1033,6 +1034,73 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
1033
1034
|
throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1034
1035
|
},
|
|
1035
1036
|
),
|
|
1037
|
+
createBillingPortal: createAuthEndpoint(
|
|
1038
|
+
"/subscription/billing-portal",
|
|
1039
|
+
{
|
|
1040
|
+
method: "POST",
|
|
1041
|
+
body: z.object({
|
|
1042
|
+
referenceId: z.string().optional(),
|
|
1043
|
+
returnUrl: z.string().default("/"),
|
|
1044
|
+
}),
|
|
1045
|
+
use: [
|
|
1046
|
+
sessionMiddleware,
|
|
1047
|
+
originCheck((ctx) => ctx.body.returnUrl),
|
|
1048
|
+
referenceMiddleware("billing-portal"),
|
|
1049
|
+
],
|
|
1050
|
+
},
|
|
1051
|
+
async (ctx) => {
|
|
1052
|
+
const { user } = ctx.context.session;
|
|
1053
|
+
const referenceId = ctx.body.referenceId || user.id;
|
|
1054
|
+
|
|
1055
|
+
let customerId = user.stripeCustomerId;
|
|
1056
|
+
|
|
1057
|
+
if (!customerId) {
|
|
1058
|
+
const subscription = await ctx.context.adapter
|
|
1059
|
+
.findMany<Subscription>({
|
|
1060
|
+
model: "subscription",
|
|
1061
|
+
where: [
|
|
1062
|
+
{
|
|
1063
|
+
field: "referenceId",
|
|
1064
|
+
value: referenceId,
|
|
1065
|
+
},
|
|
1066
|
+
],
|
|
1067
|
+
})
|
|
1068
|
+
.then((subs) =>
|
|
1069
|
+
subs.find(
|
|
1070
|
+
(sub) => sub.status === "active" || sub.status === "trialing",
|
|
1071
|
+
),
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1074
|
+
customerId = subscription?.stripeCustomerId;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (!customerId) {
|
|
1078
|
+
throw new APIError("BAD_REQUEST", {
|
|
1079
|
+
message: "No Stripe customer found for this user",
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
try {
|
|
1084
|
+
const { url } = await client.billingPortal.sessions.create({
|
|
1085
|
+
customer: customerId,
|
|
1086
|
+
return_url: getUrl(ctx, ctx.body.returnUrl),
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
return ctx.json({
|
|
1090
|
+
url,
|
|
1091
|
+
redirect: true,
|
|
1092
|
+
});
|
|
1093
|
+
} catch (error: any) {
|
|
1094
|
+
ctx.context.logger.error(
|
|
1095
|
+
"Error creating billing portal session",
|
|
1096
|
+
error,
|
|
1097
|
+
);
|
|
1098
|
+
throw new APIError("BAD_REQUEST", {
|
|
1099
|
+
message: error.message,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
),
|
|
1036
1104
|
} as const;
|
|
1037
1105
|
return {
|
|
1038
1106
|
id: "stripe",
|
package/src/stripe.test.ts
CHANGED
|
@@ -844,4 +844,40 @@ describe("stripe", async () => {
|
|
|
844
844
|
|
|
845
845
|
expect(mockStripe.customers.create).toHaveBeenCalledTimes(1);
|
|
846
846
|
});
|
|
847
|
+
|
|
848
|
+
it("should create billing portal session", async () => {
|
|
849
|
+
await authClient.signUp.email(
|
|
850
|
+
{
|
|
851
|
+
...testUser,
|
|
852
|
+
email: "billing-portal@email.com",
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
throw: true,
|
|
856
|
+
},
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
const headers = new Headers();
|
|
860
|
+
await authClient.signIn.email(
|
|
861
|
+
{
|
|
862
|
+
...testUser,
|
|
863
|
+
email: "billing-portal@email.com",
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
throw: true,
|
|
867
|
+
onSuccess: setCookieToHeader(headers),
|
|
868
|
+
},
|
|
869
|
+
);
|
|
870
|
+
const billingPortalRes = await authClient.subscription.billingPortal({
|
|
871
|
+
returnUrl: "/dashboard",
|
|
872
|
+
fetchOptions: {
|
|
873
|
+
headers,
|
|
874
|
+
},
|
|
875
|
+
});
|
|
876
|
+
expect(billingPortalRes.data?.url).toBe("https://billing.stripe.com/mock");
|
|
877
|
+
expect(billingPortalRes.data?.redirect).toBe(true);
|
|
878
|
+
expect(mockStripe.billingPortal.sessions.create).toHaveBeenCalledWith({
|
|
879
|
+
customer: expect.any(String),
|
|
880
|
+
return_url: "http://localhost:3000/dashboard",
|
|
881
|
+
});
|
|
882
|
+
});
|
|
847
883
|
});
|
package/src/types.ts
CHANGED
|
@@ -274,7 +274,8 @@ export interface StripeOptions {
|
|
|
274
274
|
| "upgrade-subscription"
|
|
275
275
|
| "list-subscription"
|
|
276
276
|
| "cancel-subscription"
|
|
277
|
-
| "restore-subscription"
|
|
277
|
+
| "restore-subscription"
|
|
278
|
+
| "billing-portal";
|
|
278
279
|
},
|
|
279
280
|
ctx: GenericEndpointContext,
|
|
280
281
|
) => Promise<boolean>;
|