@better-auth/stripe 1.7.0-beta.0 → 1.7.0-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/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -341,21 +341,24 @@ type SubscriptionOptions = {
|
|
|
341
341
|
plan: StripePlan;
|
|
342
342
|
}, ctx: GenericEndpointContext) => Promise<void>) | undefined;
|
|
343
343
|
/**
|
|
344
|
-
* A callback to run
|
|
344
|
+
* A callback to run on every subscription update webhook. Use `stripeSubscription`
|
|
345
|
+
* to read fields that are not persisted in the local subscription row.
|
|
345
346
|
* @returns
|
|
346
347
|
*/
|
|
347
348
|
onSubscriptionUpdate?: ((data: {
|
|
348
349
|
event: Stripe.Event;
|
|
350
|
+
stripeSubscription: Stripe.Subscription;
|
|
349
351
|
subscription: Subscription;
|
|
350
352
|
}) => Promise<void>) | undefined;
|
|
351
353
|
/**
|
|
352
|
-
* A callback to run
|
|
354
|
+
* A callback to run once when a subscription transitions into a pending-cancel state
|
|
355
|
+
* (e.g. `cancel_at_period_end` or a scheduled `cancel_at`).
|
|
353
356
|
* @returns
|
|
354
357
|
*/
|
|
355
358
|
onSubscriptionCancel?: ((data: {
|
|
356
|
-
event
|
|
357
|
-
subscription: Subscription;
|
|
359
|
+
event: Stripe.Event;
|
|
358
360
|
stripeSubscription: Stripe.Subscription;
|
|
361
|
+
subscription: Subscription;
|
|
359
362
|
cancellationDetails?: Stripe.Subscription.CancellationDetails | null;
|
|
360
363
|
}) => Promise<void>) | undefined;
|
|
361
364
|
/**
|
|
@@ -573,6 +576,8 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
573
576
|
}>)[];
|
|
574
577
|
}>) => Promise<{
|
|
575
578
|
session: StripeCtxSession;
|
|
579
|
+
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
580
|
+
referenceId: string;
|
|
576
581
|
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>))[];
|
|
577
582
|
}, {
|
|
578
583
|
url: string;
|
|
@@ -634,6 +639,8 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
634
639
|
}>)[];
|
|
635
640
|
}>) => Promise<{
|
|
636
641
|
session: StripeCtxSession;
|
|
642
|
+
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
643
|
+
referenceId: string;
|
|
637
644
|
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>))[];
|
|
638
645
|
}, {
|
|
639
646
|
url: string;
|
|
@@ -680,7 +687,9 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
680
687
|
}>)[];
|
|
681
688
|
}>) => Promise<{
|
|
682
689
|
session: StripeCtxSession;
|
|
683
|
-
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<
|
|
690
|
+
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
691
|
+
referenceId: string;
|
|
692
|
+
}>))[];
|
|
684
693
|
}, Stripe.Response<Stripe.Subscription>>;
|
|
685
694
|
listActiveSubscriptions: better_call0.StrictEndpoint<"/subscription/list", {
|
|
686
695
|
method: "GET";
|
|
@@ -722,7 +731,9 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
722
731
|
}>)[];
|
|
723
732
|
}>) => Promise<{
|
|
724
733
|
session: StripeCtxSession;
|
|
725
|
-
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<
|
|
734
|
+
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
735
|
+
referenceId: string;
|
|
736
|
+
}>))[];
|
|
726
737
|
}, {
|
|
727
738
|
limits: Record<string, unknown> | undefined;
|
|
728
739
|
priceId: string | undefined;
|
|
@@ -798,6 +809,8 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
798
809
|
}>)[];
|
|
799
810
|
}>) => Promise<{
|
|
800
811
|
session: StripeCtxSession;
|
|
812
|
+
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
813
|
+
referenceId: string;
|
|
801
814
|
}>) | ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>))[];
|
|
802
815
|
}, {
|
|
803
816
|
url: string;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as CheckoutSessionLocale, c as StripeOptions, d as SubscriptionOptions, f as WithActiveOrganizationId, i as CheckoutSessionLineItem, l as StripePlan, n as stripe, o as CustomerType, p as WithStripeCustomerId, r as AuthorizeReferenceAction, s as StripeCtxSession, t as StripePlugin, u as Subscription } from "./index-
|
|
1
|
+
import { a as CheckoutSessionLocale, c as StripeOptions, d as SubscriptionOptions, f as WithActiveOrganizationId, i as CheckoutSessionLineItem, l as StripePlan, n as stripe, o as CustomerType, p as WithStripeCustomerId, r as AuthorizeReferenceAction, s as StripeCtxSession, t as StripePlugin, u as Subscription } from "./index-XU8GMPhh.mjs";
|
|
2
2
|
export { AuthorizeReferenceAction, CheckoutSessionLineItem, CheckoutSessionLocale, CustomerType, StripeCtxSession, StripeOptions, StripePlan, StripePlugin, Subscription, SubscriptionOptions, WithActiveOrganizationId, WithStripeCustomerId, stripe };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as STRIPE_ERROR_CODES, t as PACKAGE_VERSION } from "./version-
|
|
1
|
+
import { n as STRIPE_ERROR_CODES, t as PACKAGE_VERSION } from "./version-Cup6oezK.mjs";
|
|
2
2
|
import { APIError, HIDE_METADATA } from "better-auth";
|
|
3
3
|
import { defu } from "defu";
|
|
4
4
|
import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
|
|
@@ -7,9 +7,27 @@ import { getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/a
|
|
|
7
7
|
import * as z from "zod";
|
|
8
8
|
import { mergeSchema } from "better-auth/db";
|
|
9
9
|
//#region src/metadata.ts
|
|
10
|
+
const UNSAFE_METADATA_KEYS = new Set([
|
|
11
|
+
"__proto__",
|
|
12
|
+
"constructor",
|
|
13
|
+
"prototype"
|
|
14
|
+
]);
|
|
10
15
|
/**
|
|
11
|
-
*
|
|
16
|
+
* Merge flat Stripe metadata objects, giving `internalFields` final priority.
|
|
17
|
+
* Drops reserved keys that could mutate the target's prototype chain.
|
|
12
18
|
*/
|
|
19
|
+
function mergeMetadata(internalFields, userMetadata) {
|
|
20
|
+
const merged = {};
|
|
21
|
+
for (const source of userMetadata) {
|
|
22
|
+
if (!source) continue;
|
|
23
|
+
for (const [key, value] of Object.entries(source)) {
|
|
24
|
+
if (UNSAFE_METADATA_KEYS.has(key)) continue;
|
|
25
|
+
merged[key] = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [key, value] of Object.entries(internalFields)) merged[key] = value;
|
|
29
|
+
return merged;
|
|
30
|
+
}
|
|
13
31
|
const customerMetadata = {
|
|
14
32
|
keys: {
|
|
15
33
|
userId: "userId",
|
|
@@ -17,7 +35,7 @@ const customerMetadata = {
|
|
|
17
35
|
customerType: "customerType"
|
|
18
36
|
},
|
|
19
37
|
set(internalFields, ...userMetadata) {
|
|
20
|
-
return
|
|
38
|
+
return mergeMetadata(internalFields, userMetadata);
|
|
21
39
|
},
|
|
22
40
|
get(metadata) {
|
|
23
41
|
return {
|
|
@@ -27,9 +45,6 @@ const customerMetadata = {
|
|
|
27
45
|
};
|
|
28
46
|
}
|
|
29
47
|
};
|
|
30
|
-
/**
|
|
31
|
-
* Subscription/Checkout metadata - set internal fields and extract typed fields.
|
|
32
|
-
*/
|
|
33
48
|
const subscriptionMetadata = {
|
|
34
49
|
keys: {
|
|
35
50
|
userId: "userId",
|
|
@@ -37,7 +52,7 @@ const subscriptionMetadata = {
|
|
|
37
52
|
referenceId: "referenceId"
|
|
38
53
|
},
|
|
39
54
|
set(internalFields, ...userMetadata) {
|
|
40
|
-
return
|
|
55
|
+
return mergeMetadata(internalFields, userMetadata);
|
|
41
56
|
},
|
|
42
57
|
get(metadata) {
|
|
43
58
|
return {
|
|
@@ -76,13 +91,13 @@ function isStripePendingCancel(stripeSub) {
|
|
|
76
91
|
}
|
|
77
92
|
/**
|
|
78
93
|
* Escapes a value for use in Stripe search queries.
|
|
79
|
-
* Stripe search query uses
|
|
80
|
-
*
|
|
94
|
+
* Stripe search query uses backslash escaping inside double-quoted
|
|
95
|
+
* string values, so literal backslashes must be escaped before quotes.
|
|
81
96
|
*
|
|
82
97
|
* @see https://docs.stripe.com/search#search-query-language
|
|
83
98
|
*/
|
|
84
99
|
function escapeStripeSearchValue(value) {
|
|
85
|
-
return value.
|
|
100
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
86
101
|
}
|
|
87
102
|
/**
|
|
88
103
|
* Resolve the quantity for a subscription by checking the seat item first,
|
|
@@ -219,13 +234,13 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
|
|
|
219
234
|
async function onSubscriptionCreated(ctx, options, event) {
|
|
220
235
|
try {
|
|
221
236
|
if (!options.subscription?.enabled) return;
|
|
222
|
-
const
|
|
223
|
-
const stripeCustomerId =
|
|
237
|
+
const stripeSubscriptionCreated = event.data.object;
|
|
238
|
+
const stripeCustomerId = stripeSubscriptionCreated.customer?.toString();
|
|
224
239
|
if (!stripeCustomerId) {
|
|
225
240
|
ctx.context.logger.warn(`Stripe webhook warning: customer.subscription.created event received without customer ID`);
|
|
226
241
|
return;
|
|
227
242
|
}
|
|
228
|
-
const { subscriptionId } = subscriptionMetadata.get(
|
|
243
|
+
const { subscriptionId } = subscriptionMetadata.get(stripeSubscriptionCreated.metadata);
|
|
229
244
|
const existingSubscription = await ctx.context.adapter.findOne({
|
|
230
245
|
model: "subscription",
|
|
231
246
|
where: subscriptionId ? [{
|
|
@@ -233,7 +248,7 @@ async function onSubscriptionCreated(ctx, options, event) {
|
|
|
233
248
|
value: subscriptionId
|
|
234
249
|
}] : [{
|
|
235
250
|
field: "stripeSubscriptionId",
|
|
236
|
-
value:
|
|
251
|
+
value: stripeSubscriptionCreated.id
|
|
237
252
|
}]
|
|
238
253
|
});
|
|
239
254
|
if (existingSubscription) {
|
|
@@ -246,9 +261,9 @@ async function onSubscriptionCreated(ctx, options, event) {
|
|
|
246
261
|
return;
|
|
247
262
|
}
|
|
248
263
|
const { referenceId, customerType } = reference;
|
|
249
|
-
const resolved = await resolvePlanItem(options,
|
|
264
|
+
const resolved = await resolvePlanItem(options, stripeSubscriptionCreated.items.data);
|
|
250
265
|
if (!resolved) {
|
|
251
|
-
ctx.context.logger.warn(`Stripe webhook warning: Subscription ${
|
|
266
|
+
ctx.context.logger.warn(`Stripe webhook warning: Subscription ${stripeSubscriptionCreated.id} has no items matching a configured plan`);
|
|
252
267
|
return;
|
|
253
268
|
}
|
|
254
269
|
const { item: subscriptionItem, plan } = resolved;
|
|
@@ -256,12 +271,12 @@ async function onSubscriptionCreated(ctx, options, event) {
|
|
|
256
271
|
ctx.context.logger.warn(`Stripe webhook warning: No matching plan found for priceId: ${subscriptionItem.price.id}`);
|
|
257
272
|
return;
|
|
258
273
|
}
|
|
259
|
-
const seats = resolveQuantity(
|
|
274
|
+
const seats = resolveQuantity(stripeSubscriptionCreated.items.data, subscriptionItem, plan.seatPriceId);
|
|
260
275
|
const periodStart = /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3);
|
|
261
276
|
const periodEnd = /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3);
|
|
262
|
-
const trial =
|
|
263
|
-
trialStart: /* @__PURE__ */ new Date(
|
|
264
|
-
trialEnd: /* @__PURE__ */ new Date(
|
|
277
|
+
const trial = stripeSubscriptionCreated.trial_start && stripeSubscriptionCreated.trial_end ? {
|
|
278
|
+
trialStart: /* @__PURE__ */ new Date(stripeSubscriptionCreated.trial_start * 1e3),
|
|
279
|
+
trialEnd: /* @__PURE__ */ new Date(stripeSubscriptionCreated.trial_end * 1e3)
|
|
265
280
|
} : {};
|
|
266
281
|
const newSubscription = await ctx.context.adapter.create({
|
|
267
282
|
model: "subscription",
|
|
@@ -270,8 +285,8 @@ async function onSubscriptionCreated(ctx, options, event) {
|
|
|
270
285
|
...plan.limits ? { limits: plan.limits } : {},
|
|
271
286
|
referenceId,
|
|
272
287
|
stripeCustomerId,
|
|
273
|
-
stripeSubscriptionId:
|
|
274
|
-
status:
|
|
288
|
+
stripeSubscriptionId: stripeSubscriptionCreated.id,
|
|
289
|
+
status: stripeSubscriptionCreated.status,
|
|
275
290
|
plan: plan.name.toLowerCase(),
|
|
276
291
|
periodStart,
|
|
277
292
|
periodEnd,
|
|
@@ -279,11 +294,11 @@ async function onSubscriptionCreated(ctx, options, event) {
|
|
|
279
294
|
billingInterval: subscriptionItem.price.recurring?.interval
|
|
280
295
|
}
|
|
281
296
|
});
|
|
282
|
-
ctx.context.logger.info(`Stripe webhook: Created subscription ${
|
|
297
|
+
ctx.context.logger.info(`Stripe webhook: Created subscription ${stripeSubscriptionCreated.id} for ${customerType} ${referenceId} from dashboard`);
|
|
283
298
|
await options.subscription.onSubscriptionCreated?.({
|
|
284
299
|
event,
|
|
285
300
|
subscription: newSubscription,
|
|
286
|
-
stripeSubscription:
|
|
301
|
+
stripeSubscription: stripeSubscriptionCreated,
|
|
287
302
|
plan
|
|
288
303
|
});
|
|
289
304
|
} catch (error) {
|
|
@@ -293,15 +308,15 @@ async function onSubscriptionCreated(ctx, options, event) {
|
|
|
293
308
|
async function onSubscriptionUpdated(ctx, options, event) {
|
|
294
309
|
try {
|
|
295
310
|
if (!options.subscription?.enabled) return;
|
|
296
|
-
const
|
|
297
|
-
const resolved = await resolvePlanItem(options,
|
|
311
|
+
const stripeSubscriptionUpdated = event.data.object;
|
|
312
|
+
const resolved = await resolvePlanItem(options, stripeSubscriptionUpdated.items.data);
|
|
298
313
|
if (!resolved) {
|
|
299
|
-
ctx.context.logger.warn(`Stripe webhook warning: Subscription ${
|
|
314
|
+
ctx.context.logger.warn(`Stripe webhook warning: Subscription ${stripeSubscriptionUpdated.id} has no items matching a configured plan`);
|
|
300
315
|
return;
|
|
301
316
|
}
|
|
302
317
|
const { item: subscriptionItem, plan } = resolved;
|
|
303
|
-
const { subscriptionId } = subscriptionMetadata.get(
|
|
304
|
-
const customerId =
|
|
318
|
+
const { subscriptionId } = subscriptionMetadata.get(stripeSubscriptionUpdated.metadata);
|
|
319
|
+
const customerId = stripeSubscriptionUpdated.customer?.toString();
|
|
305
320
|
let subscription = await ctx.context.adapter.findOne({
|
|
306
321
|
model: "subscription",
|
|
307
322
|
where: subscriptionId ? [{
|
|
@@ -309,7 +324,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
309
324
|
value: subscriptionId
|
|
310
325
|
}] : [{
|
|
311
326
|
field: "stripeSubscriptionId",
|
|
312
|
-
value:
|
|
327
|
+
value: stripeSubscriptionUpdated.id
|
|
313
328
|
}]
|
|
314
329
|
});
|
|
315
330
|
if (!subscription) {
|
|
@@ -329,12 +344,12 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
329
344
|
subscription = activeSub;
|
|
330
345
|
} else subscription = subs[0];
|
|
331
346
|
}
|
|
332
|
-
const seats = plan ? resolveQuantity(
|
|
333
|
-
const trial =
|
|
334
|
-
trialStart: /* @__PURE__ */ new Date(
|
|
335
|
-
trialEnd: /* @__PURE__ */ new Date(
|
|
347
|
+
const seats = plan ? resolveQuantity(stripeSubscriptionUpdated.items.data, subscriptionItem, plan.seatPriceId) : subscriptionItem.quantity;
|
|
348
|
+
const trial = stripeSubscriptionUpdated.trial_start && stripeSubscriptionUpdated.trial_end ? {
|
|
349
|
+
trialStart: /* @__PURE__ */ new Date(stripeSubscriptionUpdated.trial_start * 1e3),
|
|
350
|
+
trialEnd: /* @__PURE__ */ new Date(stripeSubscriptionUpdated.trial_end * 1e3)
|
|
336
351
|
} : {};
|
|
337
|
-
const
|
|
352
|
+
const subscriptionUpdated = await ctx.context.adapter.update({
|
|
338
353
|
model: "subscription",
|
|
339
354
|
update: {
|
|
340
355
|
...trial,
|
|
@@ -343,36 +358,41 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
343
358
|
limits: plan.limits
|
|
344
359
|
} : {},
|
|
345
360
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
346
|
-
status:
|
|
361
|
+
status: stripeSubscriptionUpdated.status,
|
|
347
362
|
periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
|
|
348
363
|
periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
|
|
349
|
-
cancelAtPeriodEnd:
|
|
350
|
-
cancelAt:
|
|
351
|
-
canceledAt:
|
|
352
|
-
endedAt:
|
|
364
|
+
cancelAtPeriodEnd: stripeSubscriptionUpdated.cancel_at_period_end,
|
|
365
|
+
cancelAt: stripeSubscriptionUpdated.cancel_at ? /* @__PURE__ */ new Date(stripeSubscriptionUpdated.cancel_at * 1e3) : null,
|
|
366
|
+
canceledAt: stripeSubscriptionUpdated.canceled_at ? /* @__PURE__ */ new Date(stripeSubscriptionUpdated.canceled_at * 1e3) : null,
|
|
367
|
+
endedAt: stripeSubscriptionUpdated.ended_at ? /* @__PURE__ */ new Date(stripeSubscriptionUpdated.ended_at * 1e3) : null,
|
|
353
368
|
seats,
|
|
354
|
-
stripeSubscriptionId:
|
|
369
|
+
stripeSubscriptionId: stripeSubscriptionUpdated.id,
|
|
355
370
|
billingInterval: subscriptionItem.price.recurring?.interval,
|
|
356
|
-
stripeScheduleId:
|
|
371
|
+
stripeScheduleId: stripeSubscriptionUpdated.schedule ? typeof stripeSubscriptionUpdated.schedule === "string" ? stripeSubscriptionUpdated.schedule : stripeSubscriptionUpdated.schedule.id : null
|
|
357
372
|
},
|
|
358
373
|
where: [{
|
|
359
374
|
field: "id",
|
|
360
375
|
value: subscription.id
|
|
361
376
|
}]
|
|
362
377
|
});
|
|
363
|
-
if (subscriptionUpdated
|
|
364
|
-
subscription,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
378
|
+
if (!subscriptionUpdated) {
|
|
379
|
+
ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscription.id} update returned no row (likely deleted concurrently), skipping callbacks`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (stripeSubscriptionUpdated.status === "active" && isStripePendingCancel(stripeSubscriptionUpdated) && !isPendingCancel(subscription)) await options.subscription.onSubscriptionCancel?.({
|
|
383
|
+
event,
|
|
384
|
+
subscription: subscriptionUpdated,
|
|
385
|
+
stripeSubscription: stripeSubscriptionUpdated,
|
|
386
|
+
cancellationDetails: stripeSubscriptionUpdated.cancellation_details || void 0
|
|
368
387
|
});
|
|
369
388
|
await options.subscription.onSubscriptionUpdate?.({
|
|
370
389
|
event,
|
|
371
|
-
subscription:
|
|
390
|
+
subscription: subscriptionUpdated,
|
|
391
|
+
stripeSubscription: stripeSubscriptionUpdated
|
|
372
392
|
});
|
|
373
393
|
if (plan) {
|
|
374
|
-
if (
|
|
375
|
-
if (
|
|
394
|
+
if (stripeSubscriptionUpdated.status === "active" && subscription.status === "trialing" && plan.freeTrial?.onTrialEnd) await plan.freeTrial.onTrialEnd({ subscription: subscriptionUpdated }, ctx);
|
|
395
|
+
if (stripeSubscriptionUpdated.status === "incomplete_expired" && subscription.status === "trialing" && plan.freeTrial?.onTrialExpired) await plan.freeTrial.onTrialExpired(subscriptionUpdated, ctx);
|
|
376
396
|
}
|
|
377
397
|
} catch (error) {
|
|
378
398
|
ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
|
|
@@ -381,8 +401,8 @@ async function onSubscriptionUpdated(ctx, options, event) {
|
|
|
381
401
|
async function onSubscriptionDeleted(ctx, options, event) {
|
|
382
402
|
if (!options.subscription?.enabled) return;
|
|
383
403
|
try {
|
|
384
|
-
const
|
|
385
|
-
const subscriptionId =
|
|
404
|
+
const stripeSubscriptionDeleted = event.data.object;
|
|
405
|
+
const subscriptionId = stripeSubscriptionDeleted.id;
|
|
386
406
|
const subscription = await ctx.context.adapter.findOne({
|
|
387
407
|
model: "subscription",
|
|
388
408
|
where: [{
|
|
@@ -391,11 +411,11 @@ async function onSubscriptionDeleted(ctx, options, event) {
|
|
|
391
411
|
}]
|
|
392
412
|
});
|
|
393
413
|
if (subscription) {
|
|
394
|
-
const trial =
|
|
395
|
-
trialStart: /* @__PURE__ */ new Date(
|
|
396
|
-
trialEnd: /* @__PURE__ */ new Date(
|
|
414
|
+
const trial = stripeSubscriptionDeleted.trial_start && stripeSubscriptionDeleted.trial_end ? {
|
|
415
|
+
trialStart: /* @__PURE__ */ new Date(stripeSubscriptionDeleted.trial_start * 1e3),
|
|
416
|
+
trialEnd: /* @__PURE__ */ new Date(stripeSubscriptionDeleted.trial_end * 1e3)
|
|
397
417
|
} : {};
|
|
398
|
-
await ctx.context.adapter.update({
|
|
418
|
+
const subscriptionUpdated = await ctx.context.adapter.update({
|
|
399
419
|
model: "subscription",
|
|
400
420
|
where: [{
|
|
401
421
|
field: "id",
|
|
@@ -405,17 +425,21 @@ async function onSubscriptionDeleted(ctx, options, event) {
|
|
|
405
425
|
...trial,
|
|
406
426
|
status: "canceled",
|
|
407
427
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
408
|
-
cancelAtPeriodEnd:
|
|
409
|
-
cancelAt:
|
|
410
|
-
canceledAt:
|
|
411
|
-
endedAt:
|
|
428
|
+
cancelAtPeriodEnd: stripeSubscriptionDeleted.cancel_at_period_end,
|
|
429
|
+
cancelAt: stripeSubscriptionDeleted.cancel_at ? /* @__PURE__ */ new Date(stripeSubscriptionDeleted.cancel_at * 1e3) : null,
|
|
430
|
+
canceledAt: stripeSubscriptionDeleted.canceled_at ? /* @__PURE__ */ new Date(stripeSubscriptionDeleted.canceled_at * 1e3) : null,
|
|
431
|
+
endedAt: stripeSubscriptionDeleted.ended_at ? /* @__PURE__ */ new Date(stripeSubscriptionDeleted.ended_at * 1e3) : null,
|
|
412
432
|
stripeScheduleId: null
|
|
413
433
|
}
|
|
414
434
|
});
|
|
435
|
+
if (!subscriptionUpdated) {
|
|
436
|
+
ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscription.id} update returned no row (likely deleted concurrently), skipping callbacks`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
415
439
|
await options.subscription.onSubscriptionDeleted?.({
|
|
416
440
|
event,
|
|
417
|
-
stripeSubscription:
|
|
418
|
-
subscription
|
|
441
|
+
stripeSubscription: stripeSubscriptionDeleted,
|
|
442
|
+
subscription: subscriptionUpdated
|
|
419
443
|
});
|
|
420
444
|
} else ctx.context.logger.warn(`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`);
|
|
421
445
|
} catch (error) {
|
|
@@ -427,12 +451,14 @@ async function onSubscriptionDeleted(ctx, options, event) {
|
|
|
427
451
|
const stripeSessionMiddleware = createAuthMiddleware({ use: [sessionMiddleware] }, async (ctx) => {
|
|
428
452
|
return { session: ctx.context.session };
|
|
429
453
|
});
|
|
430
|
-
const referenceMiddleware = (
|
|
454
|
+
const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx) => {
|
|
455
|
+
const subscriptionOptions = options.subscription;
|
|
431
456
|
const ctxSession = ctx.context.session;
|
|
432
457
|
if (!ctxSession) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
|
|
433
458
|
const customerType = ctx.body?.customerType || ctx.query?.customerType;
|
|
434
459
|
const explicitReferenceId = ctx.body?.referenceId || ctx.query?.referenceId;
|
|
435
460
|
if (customerType === "organization") {
|
|
461
|
+
if (!options.organization?.enabled) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_SUBSCRIPTION_NOT_ENABLED);
|
|
436
462
|
if (!subscriptionOptions.authorizeReference) {
|
|
437
463
|
ctx.context.logger.error(`Organization subscriptions require authorizeReference to be defined in your stripe plugin config.`);
|
|
438
464
|
throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.AUTHORIZE_REFERENCE_REQUIRED);
|
|
@@ -445,10 +471,10 @@ const referenceMiddleware = (subscriptionOptions, action) => createAuthMiddlewar
|
|
|
445
471
|
referenceId,
|
|
446
472
|
action
|
|
447
473
|
}, ctx)) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
|
|
448
|
-
return;
|
|
474
|
+
return { referenceId };
|
|
449
475
|
}
|
|
450
|
-
|
|
451
|
-
if (explicitReferenceId === ctxSession.user.id) return;
|
|
476
|
+
const referenceId = explicitReferenceId || ctxSession.user.id;
|
|
477
|
+
if (!explicitReferenceId || explicitReferenceId === ctxSession.user.id) return { referenceId };
|
|
452
478
|
if (!subscriptionOptions.authorizeReference) {
|
|
453
479
|
ctx.context.logger.error(`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`);
|
|
454
480
|
throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.REFERENCE_ID_NOT_ALLOWED);
|
|
@@ -456,9 +482,10 @@ const referenceMiddleware = (subscriptionOptions, action) => createAuthMiddlewar
|
|
|
456
482
|
if (!await subscriptionOptions.authorizeReference({
|
|
457
483
|
user: ctxSession.user,
|
|
458
484
|
session: ctxSession.session,
|
|
459
|
-
referenceId
|
|
485
|
+
referenceId,
|
|
460
486
|
action
|
|
461
487
|
}, ctx)) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
|
|
488
|
+
return { referenceId };
|
|
462
489
|
});
|
|
463
490
|
//#endregion
|
|
464
491
|
//#region src/routes.ts
|
|
@@ -496,19 +523,21 @@ function getUrl(ctx, url) {
|
|
|
496
523
|
return `${ctx.context.options.baseURL}${url.startsWith("/") ? url : `/${url}`}`;
|
|
497
524
|
}
|
|
498
525
|
/**
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
* - `organization`: uses activeOrganizationId from session
|
|
526
|
+
* Retrieve the subscription a row points to, or `null` if it no longer exists
|
|
527
|
+
* (`resource_missing`) so callers can treat the row as stale.
|
|
502
528
|
* @internal
|
|
503
529
|
*/
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
return
|
|
510
|
-
|
|
511
|
-
|
|
530
|
+
async function retrieveStripeSubscription(client, ctx, stripeSubscriptionId) {
|
|
531
|
+
return await client.subscriptions.retrieve(stripeSubscriptionId).catch((e) => {
|
|
532
|
+
/**
|
|
533
|
+
* @see https://docs.stripe.com/error-codes
|
|
534
|
+
*/
|
|
535
|
+
if (e?.code === "resource_missing") return null;
|
|
536
|
+
throw ctx.error("BAD_REQUEST", {
|
|
537
|
+
code: e.code,
|
|
538
|
+
message: e.message
|
|
539
|
+
});
|
|
540
|
+
});
|
|
512
541
|
}
|
|
513
542
|
const upgradeSubscriptionBodySchema = z.object({
|
|
514
543
|
plan: z.string().meta({ description: "The name of the plan to upgrade to. Eg: \"pro\"" }),
|
|
@@ -551,15 +580,19 @@ const upgradeSubscription = (options) => {
|
|
|
551
580
|
metadata: { openapi: { operationId: "upgradeSubscription" } },
|
|
552
581
|
use: [
|
|
553
582
|
stripeSessionMiddleware,
|
|
554
|
-
referenceMiddleware(
|
|
583
|
+
referenceMiddleware(options, "upgrade-subscription"),
|
|
555
584
|
originCheck((c) => {
|
|
556
|
-
return [
|
|
585
|
+
return [
|
|
586
|
+
c.body.successUrl,
|
|
587
|
+
c.body.cancelUrl,
|
|
588
|
+
c.body.returnUrl
|
|
589
|
+
];
|
|
557
590
|
})
|
|
558
591
|
]
|
|
559
592
|
}, async (ctx) => {
|
|
560
593
|
const { user, session } = ctx.context.session;
|
|
561
594
|
const customerType = ctx.body.customerType || "user";
|
|
562
|
-
const referenceId = ctx.
|
|
595
|
+
const referenceId = ctx.context.referenceId;
|
|
563
596
|
if (!user.emailVerified && subscriptionOptions.requireEmailVerification) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED);
|
|
564
597
|
const plan = await getPlanByName(options, ctx.body.plan);
|
|
565
598
|
if (!plan) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND);
|
|
@@ -651,6 +684,10 @@ const upgradeSubscription = (options) => {
|
|
|
651
684
|
break;
|
|
652
685
|
}
|
|
653
686
|
}
|
|
687
|
+
if (stripeCustomer) {
|
|
688
|
+
const ownerId = customerMetadata.get(stripeCustomer.metadata).userId;
|
|
689
|
+
if (!!ownerId && ownerId !== user.id || !user.emailVerified) stripeCustomer = void 0;
|
|
690
|
+
}
|
|
654
691
|
if (!stripeCustomer) stripeCustomer = await client.customers.create({
|
|
655
692
|
email: user.email,
|
|
656
693
|
name: user.name,
|
|
@@ -968,16 +1005,23 @@ const upgradeSubscription = (options) => {
|
|
|
968
1005
|
})).some((s) => {
|
|
969
1006
|
return !!(s.trialStart || s.trialEnd) || s.status === "trialing";
|
|
970
1007
|
}) && plan.freeTrial ? { trial_period_days: plan.freeTrial.days } : void 0;
|
|
1008
|
+
const { mode: _mode, customer: _customer, customer_email: _customer_email, success_url: _success_url, cancel_url: _cancel_url, line_items: _line_items, client_reference_id: _client_reference_id, ...additionalParams } = params?.params ?? {};
|
|
971
1009
|
const response = {
|
|
972
1010
|
...await client.checkout.sessions.create({
|
|
1011
|
+
...additionalParams,
|
|
1012
|
+
mode: "subscription",
|
|
973
1013
|
...customerId ? {
|
|
974
1014
|
customer: customerId,
|
|
975
|
-
|
|
1015
|
+
customer_email: void 0,
|
|
1016
|
+
customer_update: additionalParams.customer_update ?? (customerType !== "user" ? { address: "auto" } : {
|
|
976
1017
|
name: "auto",
|
|
977
1018
|
address: "auto"
|
|
978
|
-
}
|
|
979
|
-
} : {
|
|
980
|
-
|
|
1019
|
+
})
|
|
1020
|
+
} : {
|
|
1021
|
+
customer: void 0,
|
|
1022
|
+
customer_email: user.email
|
|
1023
|
+
},
|
|
1024
|
+
locale: ctx.body.locale ?? additionalParams.locale,
|
|
981
1025
|
success_url: getUrl(ctx, `${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(ctx.body.successUrl)}&checkoutSessionId={CHECKOUT_SESSION_ID}`),
|
|
982
1026
|
cancel_url: getUrl(ctx, ctx.body.cancelUrl),
|
|
983
1027
|
line_items: [
|
|
@@ -991,22 +1035,21 @@ const upgradeSubscription = (options) => {
|
|
|
991
1035
|
}] : [],
|
|
992
1036
|
...plan.lineItems ?? []
|
|
993
1037
|
],
|
|
1038
|
+
client_reference_id: referenceId,
|
|
994
1039
|
subscription_data: {
|
|
995
1040
|
...freeTrial,
|
|
1041
|
+
...additionalParams.subscription_data,
|
|
996
1042
|
metadata: subscriptionMetadata.set({
|
|
997
1043
|
userId: user.id,
|
|
998
1044
|
subscriptionId: subscription.id,
|
|
999
1045
|
referenceId
|
|
1000
|
-
}, ctx.body.metadata,
|
|
1046
|
+
}, ctx.body.metadata, additionalParams.subscription_data?.metadata)
|
|
1001
1047
|
},
|
|
1002
|
-
mode: "subscription",
|
|
1003
|
-
client_reference_id: referenceId,
|
|
1004
|
-
...params?.params,
|
|
1005
1048
|
metadata: subscriptionMetadata.set({
|
|
1006
1049
|
userId: user.id,
|
|
1007
1050
|
subscriptionId: subscription.id,
|
|
1008
1051
|
referenceId
|
|
1009
|
-
}, ctx.body.metadata,
|
|
1052
|
+
}, ctx.body.metadata, additionalParams.metadata)
|
|
1010
1053
|
}, params?.options).catch(async (e) => {
|
|
1011
1054
|
throw ctx.error("BAD_REQUEST", {
|
|
1012
1055
|
message: e.message,
|
|
@@ -1042,19 +1085,17 @@ const cancelSubscriptionBodySchema = z.object({
|
|
|
1042
1085
|
*/
|
|
1043
1086
|
const cancelSubscription = (options) => {
|
|
1044
1087
|
const client = options.stripeClient;
|
|
1045
|
-
const subscriptionOptions = options.subscription;
|
|
1046
1088
|
return createAuthEndpoint("/subscription/cancel", {
|
|
1047
1089
|
method: "POST",
|
|
1048
1090
|
body: cancelSubscriptionBodySchema,
|
|
1049
1091
|
metadata: { openapi: { operationId: "cancelSubscription" } },
|
|
1050
1092
|
use: [
|
|
1051
1093
|
stripeSessionMiddleware,
|
|
1052
|
-
referenceMiddleware(
|
|
1094
|
+
referenceMiddleware(options, "cancel-subscription"),
|
|
1053
1095
|
originCheck((ctx) => ctx.body.returnUrl)
|
|
1054
1096
|
]
|
|
1055
1097
|
}, async (ctx) => {
|
|
1056
|
-
const
|
|
1057
|
-
const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
|
|
1098
|
+
const referenceId = ctx.context.referenceId;
|
|
1058
1099
|
let subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
|
|
1059
1100
|
model: "subscription",
|
|
1060
1101
|
where: [{
|
|
@@ -1070,23 +1111,18 @@ const cancelSubscription = (options) => {
|
|
|
1070
1111
|
}).then((subs) => subs.find((sub) => isActiveOrTrialing(sub)));
|
|
1071
1112
|
if (ctx.body.subscriptionId && subscription && subscription.referenceId !== referenceId) subscription = void 0;
|
|
1072
1113
|
if (!subscription || !subscription.stripeCustomerId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
* from the database. This is a rare case and should not happen.
|
|
1078
|
-
*/
|
|
1079
|
-
await ctx.context.adapter.deleteMany({
|
|
1114
|
+
if (!subscription.stripeSubscriptionId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
|
|
1115
|
+
const activeSubscription = await retrieveStripeSubscription(client, ctx, subscription.stripeSubscriptionId);
|
|
1116
|
+
if (!activeSubscription || !isActiveOrTrialing(activeSubscription)) {
|
|
1117
|
+
await ctx.context.adapter.delete({
|
|
1080
1118
|
model: "subscription",
|
|
1081
1119
|
where: [{
|
|
1082
|
-
field: "
|
|
1083
|
-
value:
|
|
1120
|
+
field: "id",
|
|
1121
|
+
value: subscription.id
|
|
1084
1122
|
}]
|
|
1085
1123
|
});
|
|
1086
1124
|
throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
|
|
1087
1125
|
}
|
|
1088
|
-
const activeSubscription = activeSubscriptions.find((sub) => sub.id === subscription.stripeSubscriptionId);
|
|
1089
|
-
if (!activeSubscription) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
|
|
1090
1126
|
const { url } = await client.billingPortal.sessions.create({
|
|
1091
1127
|
customer: subscription.stripeCustomerId,
|
|
1092
1128
|
return_url: getUrl(ctx, ctx.body?.returnUrl || "/"),
|
|
@@ -1134,15 +1170,13 @@ const restoreSubscriptionBodySchema = z.object({
|
|
|
1134
1170
|
});
|
|
1135
1171
|
const restoreSubscription = (options) => {
|
|
1136
1172
|
const client = options.stripeClient;
|
|
1137
|
-
const subscriptionOptions = options.subscription;
|
|
1138
1173
|
return createAuthEndpoint("/subscription/restore", {
|
|
1139
1174
|
method: "POST",
|
|
1140
1175
|
body: restoreSubscriptionBodySchema,
|
|
1141
1176
|
metadata: { openapi: { operationId: "restoreSubscription" } },
|
|
1142
|
-
use: [stripeSessionMiddleware, referenceMiddleware(
|
|
1177
|
+
use: [stripeSessionMiddleware, referenceMiddleware(options, "restore-subscription")]
|
|
1143
1178
|
}, async (ctx) => {
|
|
1144
|
-
const
|
|
1145
|
-
const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
|
|
1179
|
+
const referenceId = ctx.context.referenceId;
|
|
1146
1180
|
let subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
|
|
1147
1181
|
model: "subscription",
|
|
1148
1182
|
where: [{
|
|
@@ -1189,8 +1223,9 @@ const restoreSubscription = (options) => {
|
|
|
1189
1223
|
const releasedSub = await client.subscriptions.retrieve(subscription.stripeSubscriptionId);
|
|
1190
1224
|
return ctx.json(releasedSub);
|
|
1191
1225
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1226
|
+
if (!subscription.stripeSubscriptionId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
|
|
1227
|
+
const activeSubscription = await retrieveStripeSubscription(client, ctx, subscription.stripeSubscriptionId);
|
|
1228
|
+
if (!activeSubscription || !isActiveOrTrialing(activeSubscription)) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
|
|
1194
1229
|
const updateParams = {};
|
|
1195
1230
|
if (activeSubscription.cancel_at) updateParams.cancel_at = "";
|
|
1196
1231
|
else if (activeSubscription.cancel_at_period_end) updateParams.cancel_at_period_end = false;
|
|
@@ -1236,15 +1271,13 @@ const listActiveSubscriptionsQuerySchema = z.optional(z.object({
|
|
|
1236
1271
|
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/stripe#api-method-subscription-list)
|
|
1237
1272
|
*/
|
|
1238
1273
|
const listActiveSubscriptions = (options) => {
|
|
1239
|
-
const subscriptionOptions = options.subscription;
|
|
1240
1274
|
return createAuthEndpoint("/subscription/list", {
|
|
1241
1275
|
method: "GET",
|
|
1242
1276
|
query: listActiveSubscriptionsQuerySchema,
|
|
1243
1277
|
metadata: { openapi: { operationId: "listActiveSubscriptions" } },
|
|
1244
|
-
use: [stripeSessionMiddleware, referenceMiddleware(
|
|
1278
|
+
use: [stripeSessionMiddleware, referenceMiddleware(options, "list-subscription")]
|
|
1245
1279
|
}, async (ctx) => {
|
|
1246
|
-
const
|
|
1247
|
-
const referenceId = ctx.query?.referenceId || getReferenceId(ctx.context.session, customerType, options);
|
|
1280
|
+
const referenceId = ctx.context.referenceId;
|
|
1248
1281
|
const subscriptions = await ctx.context.adapter.findMany({
|
|
1249
1282
|
model: "subscription",
|
|
1250
1283
|
where: [{
|
|
@@ -1277,8 +1310,7 @@ const subscriptionSuccess = (options) => {
|
|
|
1277
1310
|
use: [originCheck((ctx) => ctx.query.callbackURL)]
|
|
1278
1311
|
}, async (ctx) => {
|
|
1279
1312
|
let callbackURL = ctx.query?.callbackURL || "/";
|
|
1280
|
-
|
|
1281
|
-
if (!session) throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1313
|
+
if (!await getSessionFromCtx(ctx)) throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1282
1314
|
if (!ctx.query?.checkoutSessionId) throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1283
1315
|
/**
|
|
1284
1316
|
* Replace the Stripe {CHECKOUT_SESSION_ID} template variable in callbackURL.
|
|
@@ -1307,12 +1339,9 @@ const subscriptionSuccess = (options) => {
|
|
|
1307
1339
|
throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1308
1340
|
}
|
|
1309
1341
|
if (isActiveOrTrialing(subscription)) throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1310
|
-
const
|
|
1311
|
-
if (!
|
|
1312
|
-
const stripeSubscription = await client.subscriptions.
|
|
1313
|
-
customer: customerId,
|
|
1314
|
-
status: "active"
|
|
1315
|
-
}).then((res) => res.data[0]).catch((error) => {
|
|
1342
|
+
const stripeSubscriptionId = typeof checkoutSession.subscription === "string" ? checkoutSession.subscription : checkoutSession.subscription?.id;
|
|
1343
|
+
if (!stripeSubscriptionId || checkoutSession.payment_status === "unpaid") throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1344
|
+
const stripeSubscription = await client.subscriptions.retrieve(stripeSubscriptionId).catch((error) => {
|
|
1316
1345
|
ctx.context.logger.error("Error fetching subscription from Stripe", error);
|
|
1317
1346
|
throw ctx.redirect(getUrl(ctx, callbackURL));
|
|
1318
1347
|
});
|
|
@@ -1365,20 +1394,19 @@ const createBillingPortalBodySchema = z.object({
|
|
|
1365
1394
|
});
|
|
1366
1395
|
const createBillingPortal = (options) => {
|
|
1367
1396
|
const client = options.stripeClient;
|
|
1368
|
-
const subscriptionOptions = options.subscription;
|
|
1369
1397
|
return createAuthEndpoint("/subscription/billing-portal", {
|
|
1370
1398
|
method: "POST",
|
|
1371
1399
|
body: createBillingPortalBodySchema,
|
|
1372
1400
|
metadata: { openapi: { operationId: "createBillingPortal" } },
|
|
1373
1401
|
use: [
|
|
1374
1402
|
stripeSessionMiddleware,
|
|
1375
|
-
referenceMiddleware(
|
|
1403
|
+
referenceMiddleware(options, "billing-portal"),
|
|
1376
1404
|
originCheck((ctx) => ctx.body.returnUrl)
|
|
1377
1405
|
]
|
|
1378
1406
|
}, async (ctx) => {
|
|
1379
1407
|
const { user } = ctx.context.session;
|
|
1380
1408
|
const customerType = ctx.body.customerType || "user";
|
|
1381
|
-
const referenceId = ctx.
|
|
1409
|
+
const referenceId = ctx.context.referenceId;
|
|
1382
1410
|
let customerId;
|
|
1383
1411
|
if (customerType === "organization") {
|
|
1384
1412
|
customerId = (await ctx.context.adapter.findOne({
|
|
@@ -1635,12 +1663,11 @@ const stripe = (options) => {
|
|
|
1635
1663
|
const { organization } = data;
|
|
1636
1664
|
if (!organization.stripeCustomerId) return;
|
|
1637
1665
|
try {
|
|
1638
|
-
const
|
|
1666
|
+
for await (const sub of client.subscriptions.list({
|
|
1639
1667
|
customer: organization.stripeCustomerId,
|
|
1640
1668
|
status: "all",
|
|
1641
1669
|
limit: 100
|
|
1642
|
-
});
|
|
1643
|
-
for (const sub of subscriptions.data) if (sub.status !== "canceled" && sub.status !== "incomplete" && sub.status !== "incomplete_expired") throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION);
|
|
1670
|
+
})) if (sub.status !== "canceled" && sub.status !== "incomplete" && sub.status !== "incomplete_expired") throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION);
|
|
1644
1671
|
} catch (error) {
|
|
1645
1672
|
if (error instanceof APIError) throw error;
|
|
1646
1673
|
ctx.logger.error(`Failed to check organization subscriptions: ${error.message}`);
|
|
@@ -1745,6 +1772,10 @@ const stripe = (options) => {
|
|
|
1745
1772
|
break;
|
|
1746
1773
|
}
|
|
1747
1774
|
}
|
|
1775
|
+
if (stripeCustomer) {
|
|
1776
|
+
const ownerId = customerMetadata.get(stripeCustomer.metadata).userId;
|
|
1777
|
+
if (!!ownerId && ownerId !== user.id || !user.emailVerified) stripeCustomer = void 0;
|
|
1778
|
+
}
|
|
1748
1779
|
if (stripeCustomer) {
|
|
1749
1780
|
await ctx.context.internalAdapter.updateUser(user.id, { stripeCustomerId: stripeCustomer.id });
|
|
1750
1781
|
await options.onCustomerCreate?.({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/stripe",
|
|
3
|
-
"version": "1.7.0-beta.
|
|
3
|
+
"version": "1.7.0-beta.10",
|
|
4
4
|
"description": "Stripe plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,21 +51,21 @@
|
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"defu": "^6.1.
|
|
54
|
+
"defu": "^6.1.5",
|
|
55
55
|
"zod": "^4.3.6"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"better-call": "1.3.
|
|
58
|
+
"better-call": "1.3.7",
|
|
59
59
|
"stripe": "^22.0.1",
|
|
60
60
|
"tsdown": "0.21.1",
|
|
61
|
-
"@better-auth/core": "1.7.0-beta.
|
|
62
|
-
"better-auth": "1.7.0-beta.
|
|
61
|
+
"@better-auth/core": "1.7.0-beta.10",
|
|
62
|
+
"better-auth": "1.7.0-beta.10"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"better-call": "1.3.
|
|
65
|
+
"better-call": "1.3.7",
|
|
66
66
|
"stripe": "^18 || ^19 || ^20 || ^21 || ^22",
|
|
67
|
-
"@better-auth/core": "^1.7.0-beta.
|
|
68
|
-
"better-auth": "^1.7.0-beta.
|
|
67
|
+
"@better-auth/core": "^1.7.0-beta.10",
|
|
68
|
+
"better-auth": "^1.7.0-beta.10"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsdown",
|