@better-auth/stripe 1.4.10-beta.1 → 1.4.11-beta.1

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/index.mjs CHANGED
@@ -1,13 +1,37 @@
1
- import { t as STRIPE_ERROR_CODES$1 } from "./error-codes-qqooUh6R.mjs";
2
- import { defineErrorCodes } from "@better-auth/core/utils";
1
+ import { APIError, HIDE_METADATA } from "better-auth";
3
2
  import { defu } from "defu";
3
+ import { defineErrorCodes } from "@better-auth/core/utils";
4
4
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
5
- import { APIError } from "@better-auth/core/error";
6
- import { HIDE_METADATA, logger } from "better-auth";
7
5
  import { APIError as APIError$1, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
8
6
  import * as z from "zod/v4";
9
7
  import { mergeSchema } from "better-auth/db";
10
8
 
9
+ //#region src/error-codes.ts
10
+ const STRIPE_ERROR_CODES = defineErrorCodes({
11
+ UNAUTHORIZED: "Unauthorized access",
12
+ INVALID_REQUEST_BODY: "Invalid request body",
13
+ SUBSCRIPTION_NOT_FOUND: "Subscription not found",
14
+ SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
15
+ ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
16
+ REFERENCE_ID_NOT_ALLOWED: "Reference id is not allowed",
17
+ CUSTOMER_NOT_FOUND: "Stripe customer not found for this user",
18
+ UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
19
+ UNABLE_TO_CREATE_BILLING_PORTAL: "Unable to create billing portal session",
20
+ STRIPE_SIGNATURE_NOT_FOUND: "Stripe signature not found",
21
+ STRIPE_WEBHOOK_SECRET_NOT_FOUND: "Stripe webhook secret not found",
22
+ STRIPE_WEBHOOK_ERROR: "Stripe webhook error",
23
+ FAILED_TO_CONSTRUCT_STRIPE_EVENT: "Failed to construct Stripe event",
24
+ FAILED_TO_FETCH_PLANS: "Failed to fetch plans",
25
+ EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan",
26
+ SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
27
+ SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION: "Subscription is not scheduled for cancellation",
28
+ ORGANIZATION_NOT_FOUND: "Organization not found",
29
+ ORGANIZATION_SUBSCRIPTION_NOT_ENABLED: "Organization subscription is not enabled",
30
+ ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION: "Cannot delete organization with active subscription",
31
+ ORGANIZATION_REFERENCE_ID_REQUIRED: "Reference ID is required. Provide referenceId or set activeOrganizationId in session"
32
+ });
33
+
34
+ //#endregion
11
35
  //#region src/utils.ts
12
36
  async function getPlans(subscriptionOptions) {
13
37
  if (subscriptionOptions?.enabled) return typeof subscriptionOptions.plans === "function" ? await subscriptionOptions.plans() : subscriptionOptions.plans;
@@ -19,21 +43,86 @@ async function getPlanByPriceInfo(options, priceId, priceLookupKey) {
19
43
  async function getPlanByName(options, name) {
20
44
  return await getPlans(options.subscription).then((res) => res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()));
21
45
  }
46
+ /**
47
+ * Checks if a subscription is in an available state (active or trialing)
48
+ */
49
+ function isActiveOrTrialing(sub) {
50
+ return sub.status === "active" || sub.status === "trialing";
51
+ }
52
+ /**
53
+ * Check if a subscription is scheduled to be canceled (DB subscription object)
54
+ */
55
+ function isPendingCancel(sub) {
56
+ return !!(sub.cancelAtPeriodEnd || sub.cancelAt);
57
+ }
58
+ /**
59
+ * Check if a Stripe subscription is scheduled to be canceled (Stripe API response)
60
+ */
61
+ function isStripePendingCancel(stripeSub) {
62
+ return !!(stripeSub.cancel_at_period_end || stripeSub.cancel_at);
63
+ }
64
+ /**
65
+ * Escapes a value for use in Stripe search queries.
66
+ * Stripe search query uses double quotes for string values,
67
+ * and double quotes within the value need to be escaped with backslash.
68
+ *
69
+ * @see https://docs.stripe.com/search#search-query-language
70
+ */
71
+ function escapeStripeSearchValue(value) {
72
+ return value.replace(/"/g, "\\\"");
73
+ }
22
74
 
23
75
  //#endregion
24
76
  //#region src/hooks.ts
77
+ /**
78
+ * Find organization or user by stripeCustomerId.
79
+ * @internal
80
+ */
81
+ async function findReferenceByStripeCustomerId(ctx, options, stripeCustomerId) {
82
+ if (options.organization?.enabled) {
83
+ const org = await ctx.context.adapter.findOne({
84
+ model: "organization",
85
+ where: [{
86
+ field: "stripeCustomerId",
87
+ value: stripeCustomerId
88
+ }]
89
+ });
90
+ if (org) return {
91
+ customerType: "organization",
92
+ referenceId: org.id
93
+ };
94
+ }
95
+ const user$1 = await ctx.context.adapter.findOne({
96
+ model: "user",
97
+ where: [{
98
+ field: "stripeCustomerId",
99
+ value: stripeCustomerId
100
+ }]
101
+ });
102
+ if (user$1) return {
103
+ customerType: "user",
104
+ referenceId: user$1.id
105
+ };
106
+ return null;
107
+ }
25
108
  async function onCheckoutSessionCompleted(ctx, options, event) {
26
109
  try {
27
110
  const client = options.stripeClient;
28
111
  const checkoutSession = event.data.object;
29
112
  if (checkoutSession.mode === "setup" || !options.subscription?.enabled) return;
30
113
  const subscription = await client.subscriptions.retrieve(checkoutSession.subscription);
31
- const priceId = subscription.items.data[0]?.price.id;
32
- const plan = await getPlanByPriceInfo(options, priceId, subscription.items.data[0]?.price.lookup_key || null);
114
+ const subscriptionItem = subscription.items.data[0];
115
+ if (!subscriptionItem) {
116
+ ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscription.id} has no items`);
117
+ return;
118
+ }
119
+ const priceId = subscriptionItem.price.id;
120
+ const priceLookupKey = subscriptionItem.price.lookup_key;
121
+ const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
33
122
  if (plan) {
34
123
  const referenceId = checkoutSession?.client_reference_id || checkoutSession?.metadata?.referenceId;
35
124
  const subscriptionId = checkoutSession?.metadata?.subscriptionId;
36
- const seats = subscription.items.data[0].quantity;
125
+ const seats = subscriptionItem.quantity;
37
126
  if (referenceId && subscriptionId) {
38
127
  const trial = subscription.trial_start && subscription.trial_end ? {
39
128
  trialStart: /* @__PURE__ */ new Date(subscription.trial_start * 1e3),
@@ -45,9 +134,13 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
45
134
  plan: plan.name.toLowerCase(),
46
135
  status: subscription.status,
47
136
  updatedAt: /* @__PURE__ */ new Date(),
48
- periodStart: /* @__PURE__ */ new Date(subscription.items.data[0].current_period_start * 1e3),
49
- periodEnd: /* @__PURE__ */ new Date(subscription.items.data[0].current_period_end * 1e3),
137
+ periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
138
+ periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
50
139
  stripeSubscriptionId: checkoutSession.subscription,
140
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
141
+ cancelAt: subscription.cancel_at ? /* @__PURE__ */ new Date(subscription.cancel_at * 1e3) : null,
142
+ canceledAt: subscription.canceled_at ? /* @__PURE__ */ new Date(subscription.canceled_at * 1e3) : null,
143
+ endedAt: subscription.ended_at ? /* @__PURE__ */ new Date(subscription.ended_at * 1e3) : null,
51
144
  seats,
52
145
  ...trial
53
146
  },
@@ -74,15 +167,95 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
74
167
  }
75
168
  }
76
169
  } catch (e) {
77
- logger.error(`Stripe webhook failed. Error: ${e.message}`);
170
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${e.message}`);
171
+ }
172
+ }
173
+ async function onSubscriptionCreated(ctx, options, event) {
174
+ try {
175
+ if (!options.subscription?.enabled) return;
176
+ const subscriptionCreated = event.data.object;
177
+ const stripeCustomerId = subscriptionCreated.customer?.toString();
178
+ if (!stripeCustomerId) {
179
+ ctx.context.logger.warn(`Stripe webhook warning: customer.subscription.created event received without customer ID`);
180
+ return;
181
+ }
182
+ const subscriptionId = subscriptionCreated.metadata?.subscriptionId;
183
+ const existingSubscription = await ctx.context.adapter.findOne({
184
+ model: "subscription",
185
+ where: subscriptionId ? [{
186
+ field: "id",
187
+ value: subscriptionId
188
+ }] : [{
189
+ field: "stripeSubscriptionId",
190
+ value: subscriptionCreated.id
191
+ }]
192
+ });
193
+ if (existingSubscription) {
194
+ ctx.context.logger.info(`Stripe webhook: Subscription already exists in database (id: ${existingSubscription.id}), skipping creation`);
195
+ return;
196
+ }
197
+ const reference = await findReferenceByStripeCustomerId(ctx, options, stripeCustomerId);
198
+ if (!reference) {
199
+ ctx.context.logger.warn(`Stripe webhook warning: No user or organization found with stripeCustomerId: ${stripeCustomerId}`);
200
+ return;
201
+ }
202
+ const { referenceId, customerType } = reference;
203
+ const subscriptionItem = subscriptionCreated.items.data[0];
204
+ if (!subscriptionItem) {
205
+ ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscriptionCreated.id} has no items`);
206
+ return;
207
+ }
208
+ const priceId = subscriptionItem.price.id;
209
+ const plan = await getPlanByPriceInfo(options, priceId, subscriptionItem.price.lookup_key || null);
210
+ if (!plan) {
211
+ ctx.context.logger.warn(`Stripe webhook warning: No matching plan found for priceId: ${priceId}`);
212
+ return;
213
+ }
214
+ const seats = subscriptionItem.quantity;
215
+ const periodStart = /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3);
216
+ const periodEnd = /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3);
217
+ const trial = subscriptionCreated.trial_start && subscriptionCreated.trial_end ? {
218
+ trialStart: /* @__PURE__ */ new Date(subscriptionCreated.trial_start * 1e3),
219
+ trialEnd: /* @__PURE__ */ new Date(subscriptionCreated.trial_end * 1e3)
220
+ } : {};
221
+ const newSubscription = await ctx.context.adapter.create({
222
+ model: "subscription",
223
+ data: {
224
+ referenceId,
225
+ stripeCustomerId,
226
+ stripeSubscriptionId: subscriptionCreated.id,
227
+ status: subscriptionCreated.status,
228
+ plan: plan.name.toLowerCase(),
229
+ periodStart,
230
+ periodEnd,
231
+ seats,
232
+ ...plan.limits ? { limits: plan.limits } : {},
233
+ ...trial
234
+ }
235
+ });
236
+ ctx.context.logger.info(`Stripe webhook: Created subscription ${subscriptionCreated.id} for ${customerType} ${referenceId} from dashboard`);
237
+ await options.subscription.onSubscriptionCreated?.({
238
+ event,
239
+ subscription: newSubscription,
240
+ stripeSubscription: subscriptionCreated,
241
+ plan
242
+ });
243
+ } catch (error) {
244
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
78
245
  }
79
246
  }
80
247
  async function onSubscriptionUpdated(ctx, options, event) {
81
248
  try {
82
249
  if (!options.subscription?.enabled) return;
83
250
  const subscriptionUpdated = event.data.object;
84
- const priceId = subscriptionUpdated.items.data[0].price.id;
85
- const plan = await getPlanByPriceInfo(options, priceId, subscriptionUpdated.items.data[0].price.lookup_key || null);
251
+ const subscriptionItem = subscriptionUpdated.items.data[0];
252
+ if (!subscriptionItem) {
253
+ ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscriptionUpdated.id} has no items`);
254
+ return;
255
+ }
256
+ const priceId = subscriptionItem.price.id;
257
+ const priceLookupKey = subscriptionItem.price.lookup_key;
258
+ const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
86
259
  const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
87
260
  const customerId = subscriptionUpdated.customer?.toString();
88
261
  let subscription = await ctx.context.adapter.findOne({
@@ -104,15 +277,14 @@ async function onSubscriptionUpdated(ctx, options, event) {
104
277
  }]
105
278
  });
106
279
  if (subs.length > 1) {
107
- const activeSub = subs.find((sub) => sub.status === "active" || sub.status === "trialing");
280
+ const activeSub = subs.find((sub) => isActiveOrTrialing(sub));
108
281
  if (!activeSub) {
109
- logger.warn(`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no active subscription is found`);
282
+ ctx.context.logger.warn(`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no active subscription is found`);
110
283
  return;
111
284
  }
112
285
  subscription = activeSub;
113
286
  } else subscription = subs[0];
114
287
  }
115
- const seats = subscriptionUpdated.items.data[0].quantity;
116
288
  const updatedSubscription = await ctx.context.adapter.update({
117
289
  model: "subscription",
118
290
  update: {
@@ -122,10 +294,13 @@ async function onSubscriptionUpdated(ctx, options, event) {
122
294
  } : {},
123
295
  updatedAt: /* @__PURE__ */ new Date(),
124
296
  status: subscriptionUpdated.status,
125
- periodStart: /* @__PURE__ */ new Date(subscriptionUpdated.items.data[0].current_period_start * 1e3),
126
- periodEnd: /* @__PURE__ */ new Date(subscriptionUpdated.items.data[0].current_period_end * 1e3),
297
+ periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
298
+ periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
127
299
  cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
128
- seats,
300
+ cancelAt: subscriptionUpdated.cancel_at ? /* @__PURE__ */ new Date(subscriptionUpdated.cancel_at * 1e3) : null,
301
+ canceledAt: subscriptionUpdated.canceled_at ? /* @__PURE__ */ new Date(subscriptionUpdated.canceled_at * 1e3) : null,
302
+ endedAt: subscriptionUpdated.ended_at ? /* @__PURE__ */ new Date(subscriptionUpdated.ended_at * 1e3) : null,
303
+ seats: subscriptionItem.quantity,
129
304
  stripeSubscriptionId: subscriptionUpdated.id
130
305
  },
131
306
  where: [{
@@ -133,7 +308,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
133
308
  value: subscription.id
134
309
  }]
135
310
  });
136
- if (subscriptionUpdated.status === "active" && subscriptionUpdated.cancel_at_period_end && !subscription.cancelAtPeriodEnd) await options.subscription.onSubscriptionCancel?.({
311
+ if (subscriptionUpdated.status === "active" && isStripePendingCancel(subscriptionUpdated) && !isPendingCancel(subscription)) await options.subscription.onSubscriptionCancel?.({
137
312
  subscription,
138
313
  cancellationDetails: subscriptionUpdated.cancellation_details || void 0,
139
314
  stripeSubscription: subscriptionUpdated,
@@ -148,7 +323,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
148
323
  if (subscriptionUpdated.status === "incomplete_expired" && subscription.status === "trialing" && plan.freeTrial?.onTrialExpired) await plan.freeTrial.onTrialExpired(subscription, ctx);
149
324
  }
150
325
  } catch (error) {
151
- logger.error(`Stripe webhook failed. Error: ${error}`);
326
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
152
327
  }
153
328
  }
154
329
  async function onSubscriptionDeleted(ctx, options, event) {
@@ -172,7 +347,11 @@ async function onSubscriptionDeleted(ctx, options, event) {
172
347
  }],
173
348
  update: {
174
349
  status: "canceled",
175
- updatedAt: /* @__PURE__ */ new Date()
350
+ updatedAt: /* @__PURE__ */ new Date(),
351
+ cancelAtPeriodEnd: subscriptionDeleted.cancel_at_period_end,
352
+ cancelAt: subscriptionDeleted.cancel_at ? /* @__PURE__ */ new Date(subscriptionDeleted.cancel_at * 1e3) : null,
353
+ canceledAt: subscriptionDeleted.canceled_at ? /* @__PURE__ */ new Date(subscriptionDeleted.canceled_at * 1e3) : null,
354
+ endedAt: subscriptionDeleted.ended_at ? /* @__PURE__ */ new Date(subscriptionDeleted.ended_at * 1e3) : null
176
355
  }
177
356
  });
178
357
  await options.subscription.onSubscriptionDeleted?.({
@@ -180,41 +359,94 @@ async function onSubscriptionDeleted(ctx, options, event) {
180
359
  stripeSubscription: subscriptionDeleted,
181
360
  subscription
182
361
  });
183
- } else logger.warn(`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`);
362
+ } else ctx.context.logger.warn(`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`);
184
363
  } catch (error) {
185
- logger.error(`Stripe webhook failed. Error: ${error}`);
364
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
186
365
  }
187
366
  }
188
367
 
189
368
  //#endregion
190
369
  //#region src/middleware.ts
370
+ const stripeSessionMiddleware = createAuthMiddleware({ use: [sessionMiddleware] }, async (ctx) => {
371
+ return { session: ctx.context.session };
372
+ });
191
373
  const referenceMiddleware = (subscriptionOptions, action) => createAuthMiddleware(async (ctx) => {
192
- const session = ctx.context.session;
193
- if (!session) throw new APIError$1("UNAUTHORIZED");
194
- const referenceId = ctx.body?.referenceId || ctx.query?.referenceId || session.user.id;
195
- if (referenceId !== session.user.id && !subscriptionOptions.authorizeReference) {
196
- logger.error(`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`);
197
- throw new APIError$1("BAD_REQUEST", { message: "Reference id is not allowed. Read server logs for more details." });
374
+ const ctxSession = ctx.context.session;
375
+ if (!ctxSession) throw new APIError$1("UNAUTHORIZED", { message: STRIPE_ERROR_CODES.UNAUTHORIZED });
376
+ const customerType = ctx.body?.customerType || ctx.query?.customerType;
377
+ const explicitReferenceId = ctx.body?.referenceId || ctx.query?.referenceId;
378
+ if (customerType === "organization") {
379
+ if (!subscriptionOptions.authorizeReference) {
380
+ ctx.context.logger.error(`Organization subscriptions require authorizeReference to be defined in your stripe plugin config.`);
381
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_SUBSCRIPTION_NOT_ENABLED);
382
+ }
383
+ const referenceId = explicitReferenceId || ctxSession.session.activeOrganizationId;
384
+ if (!referenceId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_REFERENCE_ID_REQUIRED);
385
+ if (!await subscriptionOptions.authorizeReference({
386
+ user: ctxSession.user,
387
+ session: ctxSession.session,
388
+ referenceId,
389
+ action
390
+ }, ctx)) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
391
+ return;
198
392
  }
199
- /**
200
- * if referenceId is the same as the active session user's id
201
- */
202
- const sameReference = ctx.query?.referenceId === session.user.id || ctx.body?.referenceId === session.user.id;
203
- if (!(ctx.body?.referenceId || ctx.query?.referenceId ? await subscriptionOptions.authorizeReference?.({
204
- user: session.user,
205
- session: session.session,
206
- referenceId,
393
+ if (!explicitReferenceId) return;
394
+ if (explicitReferenceId === ctxSession.user.id) return;
395
+ if (!subscriptionOptions.authorizeReference) {
396
+ ctx.context.logger.error(`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`);
397
+ throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.REFERENCE_ID_NOT_ALLOWED });
398
+ }
399
+ if (!await subscriptionOptions.authorizeReference({
400
+ user: ctxSession.user,
401
+ session: ctxSession.session,
402
+ referenceId: explicitReferenceId,
207
403
  action
208
- }, ctx) || sameReference : true)) throw new APIError$1("UNAUTHORIZED", { message: "Unauthorized" });
404
+ }, ctx)) throw new APIError$1("UNAUTHORIZED", { message: STRIPE_ERROR_CODES.UNAUTHORIZED });
209
405
  });
210
406
 
211
407
  //#endregion
212
408
  //#region src/routes.ts
409
+ /**
410
+ * Converts a relative URL to an absolute URL using baseURL.
411
+ * @internal
412
+ */
413
+ function getUrl(ctx, url) {
414
+ if (/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(url)) return url;
415
+ return `${ctx.context.options.baseURL}${url.startsWith("/") ? url : `/${url}`}`;
416
+ }
417
+ /**
418
+ * Resolves a Stripe price ID from a lookup key.
419
+ * @internal
420
+ */
421
+ async function resolvePriceIdFromLookupKey(stripeClient, lookupKey) {
422
+ if (!lookupKey) return void 0;
423
+ return (await stripeClient.prices.list({
424
+ lookup_keys: [lookupKey],
425
+ active: true,
426
+ limit: 1
427
+ })).data[0]?.id;
428
+ }
429
+ /**
430
+ * Determines the reference ID based on customer type.
431
+ * - `user` (default): uses userId
432
+ * - `organization`: uses activeOrganizationId from session
433
+ * @internal
434
+ */
435
+ function getReferenceId(ctxSession, customerType, options) {
436
+ const { user: user$1, session } = ctxSession;
437
+ if ((customerType || "user") === "organization") {
438
+ if (!options.organization?.enabled) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_SUBSCRIPTION_NOT_ENABLED);
439
+ if (!session.activeOrganizationId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_NOT_FOUND);
440
+ return session.activeOrganizationId;
441
+ }
442
+ return user$1.id;
443
+ }
213
444
  const upgradeSubscriptionBodySchema = z.object({
214
445
  plan: z.string().meta({ description: "The name of the plan to upgrade to. Eg: \"pro\"" }),
215
446
  annual: z.boolean().meta({ description: "Whether to upgrade to an annual plan. Eg: true" }).optional(),
216
- referenceId: z.string().meta({ description: "Reference id of the subscription to upgrade. Eg: \"123\"" }).optional(),
447
+ referenceId: z.string().meta({ description: "Reference ID for the subscription. Eg: \"org_123\"" }).optional(),
217
448
  subscriptionId: z.string().meta({ description: "The Stripe subscription ID to upgrade. Eg: \"sub_1ABC2DEF3GHI4JKL\"" }).optional(),
449
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
218
450
  metadata: z.record(z.string(), z.any()).optional(),
219
451
  seats: z.number().meta({ description: "Number of seats to upgrade to (if applicable). Eg: 1" }).optional(),
220
452
  successUrl: z.string().meta({ description: "Callback URL to redirect back after successful subscription. Eg: \"https://example.com/success\"" }).default("/"),
@@ -245,18 +477,19 @@ const upgradeSubscription = (options) => {
245
477
  body: upgradeSubscriptionBodySchema,
246
478
  metadata: { openapi: { operationId: "upgradeSubscription" } },
247
479
  use: [
248
- sessionMiddleware,
480
+ stripeSessionMiddleware,
481
+ referenceMiddleware(subscriptionOptions, "upgrade-subscription"),
249
482
  originCheck((c) => {
250
483
  return [c.body.successUrl, c.body.cancelUrl];
251
- }),
252
- referenceMiddleware(subscriptionOptions, "upgrade-subscription")
484
+ })
253
485
  ]
254
486
  }, async (ctx) => {
255
487
  const { user: user$1, session } = ctx.context.session;
256
- if (!user$1.emailVerified && subscriptionOptions.requireEmailVerification) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.EMAIL_VERIFICATION_REQUIRED);
257
- const referenceId = ctx.body.referenceId || user$1.id;
488
+ const customerType = ctx.body.customerType || "user";
489
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
490
+ if (!user$1.emailVerified && subscriptionOptions.requireEmailVerification) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED });
258
491
  const plan = await getPlanByName(options, ctx.body.plan);
259
- if (!plan) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_PLAN_NOT_FOUND);
492
+ if (!plan) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND });
260
493
  let subscriptionToUpdate = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
261
494
  model: "subscription",
262
495
  where: [{
@@ -271,49 +504,104 @@ const upgradeSubscription = (options) => {
271
504
  }]
272
505
  }) : null;
273
506
  if (ctx.body.subscriptionId && subscriptionToUpdate && subscriptionToUpdate.referenceId !== referenceId) subscriptionToUpdate = null;
274
- if (ctx.body.subscriptionId && !subscriptionToUpdate) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
275
- let customerId = subscriptionToUpdate?.stripeCustomerId || user$1.stripeCustomerId;
276
- if (!customerId) try {
277
- let stripeCustomer = (await client.customers.list({
278
- email: user$1.email,
279
- limit: 1
280
- })).data[0];
281
- if (!stripeCustomer) stripeCustomer = await client.customers.create({
282
- email: user$1.email,
283
- name: user$1.name,
284
- metadata: {
285
- ...ctx.body.metadata,
286
- userId: user$1.id
507
+ if (ctx.body.subscriptionId && !subscriptionToUpdate) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
508
+ let customerId;
509
+ if (customerType === "organization") {
510
+ customerId = subscriptionToUpdate?.stripeCustomerId;
511
+ if (!customerId) {
512
+ const org = await ctx.context.adapter.findOne({
513
+ model: "organization",
514
+ where: [{
515
+ field: "id",
516
+ value: referenceId
517
+ }]
518
+ });
519
+ if (!org) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.ORGANIZATION_NOT_FOUND });
520
+ customerId = org.stripeCustomerId;
521
+ if (!customerId) try {
522
+ let stripeCustomer = (await client.customers.search({
523
+ query: `metadata["organizationId"]:"${org.id}"`,
524
+ limit: 1
525
+ })).data[0];
526
+ if (!stripeCustomer) {
527
+ let extraCreateParams = {};
528
+ if (options.organization?.getCustomerCreateParams) extraCreateParams = await options.organization.getCustomerCreateParams(org, ctx);
529
+ const customerParams = defu({
530
+ name: org.name,
531
+ metadata: {
532
+ ...ctx.body.metadata,
533
+ organizationId: org.id,
534
+ customerType: "organization"
535
+ }
536
+ }, extraCreateParams);
537
+ stripeCustomer = await client.customers.create(customerParams);
538
+ await options.organization?.onCustomerCreate?.({
539
+ stripeCustomer,
540
+ organization: {
541
+ ...org,
542
+ stripeCustomerId: stripeCustomer.id
543
+ }
544
+ }, ctx);
545
+ }
546
+ await ctx.context.adapter.update({
547
+ model: "organization",
548
+ update: { stripeCustomerId: stripeCustomer.id },
549
+ where: [{
550
+ field: "id",
551
+ value: org.id
552
+ }]
553
+ });
554
+ customerId = stripeCustomer.id;
555
+ } catch (e) {
556
+ ctx.context.logger.error(e);
557
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER);
287
558
  }
288
- });
289
- await ctx.context.adapter.update({
290
- model: "user",
291
- update: { stripeCustomerId: stripeCustomer.id },
292
- where: [{
293
- field: "id",
294
- value: user$1.id
295
- }]
296
- });
297
- customerId = stripeCustomer.id;
298
- } catch (e) {
299
- ctx.context.logger.error(e);
300
- throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.UNABLE_TO_CREATE_CUSTOMER);
559
+ }
560
+ } else {
561
+ customerId = subscriptionToUpdate?.stripeCustomerId || user$1.stripeCustomerId;
562
+ if (!customerId) try {
563
+ let stripeCustomer = (await client.customers.search({
564
+ query: `email:"${escapeStripeSearchValue(user$1.email)}" AND -metadata["customerType"]:"organization"`,
565
+ limit: 1
566
+ })).data[0];
567
+ if (!stripeCustomer) stripeCustomer = await client.customers.create({
568
+ email: user$1.email,
569
+ name: user$1.name,
570
+ metadata: {
571
+ ...ctx.body.metadata,
572
+ userId: user$1.id,
573
+ customerType: "user"
574
+ }
575
+ });
576
+ await ctx.context.adapter.update({
577
+ model: "user",
578
+ update: { stripeCustomerId: stripeCustomer.id },
579
+ where: [{
580
+ field: "id",
581
+ value: user$1.id
582
+ }]
583
+ });
584
+ customerId = stripeCustomer.id;
585
+ } catch (e) {
586
+ ctx.context.logger.error(e);
587
+ throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER });
588
+ }
301
589
  }
302
590
  const subscriptions$1 = subscriptionToUpdate ? [subscriptionToUpdate] : await ctx.context.adapter.findMany({
303
591
  model: "subscription",
304
592
  where: [{
305
593
  field: "referenceId",
306
- value: ctx.body.referenceId || user$1.id
594
+ value: referenceId
307
595
  }]
308
596
  });
309
- const activeOrTrialingSubscription = subscriptions$1.find((sub) => sub.status === "active" || sub.status === "trialing");
310
- const activeSubscription = (await client.subscriptions.list({ customer: customerId }).then((res) => res.data.filter((sub) => sub.status === "active" || sub.status === "trialing"))).find((sub) => {
597
+ const activeOrTrialingSubscription = subscriptions$1.find((sub) => isActiveOrTrialing(sub));
598
+ const activeSubscription = (await client.subscriptions.list({ customer: customerId }).then((res) => res.data.filter((sub) => isActiveOrTrialing(sub)))).find((sub) => {
311
599
  if (subscriptionToUpdate?.stripeSubscriptionId || ctx.body.subscriptionId) return sub.id === subscriptionToUpdate?.stripeSubscriptionId || sub.id === ctx.body.subscriptionId;
312
600
  if (activeOrTrialingSubscription?.stripeSubscriptionId) return sub.id === activeOrTrialingSubscription.stripeSubscriptionId;
313
601
  return false;
314
602
  });
315
603
  const incompleteSubscription = subscriptions$1.find((sub) => sub.status === "incomplete");
316
- if (activeOrTrialingSubscription && activeOrTrialingSubscription.status === "active" && activeOrTrialingSubscription.plan === ctx.body.plan && activeOrTrialingSubscription.seats === (ctx.body.seats || 1)) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.ALREADY_SUBSCRIBED_PLAN);
604
+ if (activeOrTrialingSubscription && activeOrTrialingSubscription.status === "active" && activeOrTrialingSubscription.plan === ctx.body.plan && activeOrTrialingSubscription.seats === (ctx.body.seats || 1)) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN });
317
605
  if (activeSubscription && customerId) {
318
606
  let dbSubscription = await ctx.context.adapter.findOne({
319
607
  model: "subscription",
@@ -371,7 +659,7 @@ const upgradeSubscription = (options) => {
371
659
  });
372
660
  return ctx.json({
373
661
  url,
374
- redirect: true
662
+ redirect: !ctx.body.disableRedirect
375
663
  });
376
664
  }
377
665
  let subscription = activeOrTrialingSubscription || incompleteSubscription;
@@ -399,7 +687,7 @@ const upgradeSubscription = (options) => {
399
687
  });
400
688
  if (!subscription) {
401
689
  ctx.context.logger.error("Subscription ID not found");
402
- throw new APIError("INTERNAL_SERVER_ERROR");
690
+ throw new APIError$1("NOT_FOUND", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
403
691
  }
404
692
  const params = await subscriptionOptions.getCheckoutSessionParams?.({
405
693
  user: user$1,
@@ -407,7 +695,13 @@ const upgradeSubscription = (options) => {
407
695
  plan,
408
696
  subscription
409
697
  }, ctx.request, ctx);
410
- const freeTrial = !subscriptions$1.some((s) => {
698
+ const freeTrial = !(await ctx.context.adapter.findMany({
699
+ model: "subscription",
700
+ where: [{
701
+ field: "referenceId",
702
+ value: referenceId
703
+ }]
704
+ })).some((s) => {
411
705
  return !!(s.trialStart || s.trialEnd) || s.status === "trialing";
412
706
  }) && plan.freeTrial ? { trial_period_days: plan.freeTrial.days } : void 0;
413
707
  let priceIdToUse = void 0;
@@ -421,26 +715,36 @@ const upgradeSubscription = (options) => {
421
715
  const checkoutSession = await client.checkout.sessions.create({
422
716
  ...customerId ? {
423
717
  customer: customerId,
424
- customer_update: {
718
+ customer_update: customerType !== "user" ? { address: "auto" } : {
425
719
  name: "auto",
426
720
  address: "auto"
427
721
  }
428
- } : { customer_email: session.user.email },
722
+ } : { customer_email: user$1.email },
429
723
  success_url: getUrl(ctx, `${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(ctx.body.successUrl)}&subscriptionId=${encodeURIComponent(subscription.id)}`),
430
724
  cancel_url: getUrl(ctx, ctx.body.cancelUrl),
431
725
  line_items: [{
432
726
  price: priceIdToUse,
433
727
  quantity: ctx.body.seats || 1
434
728
  }],
435
- subscription_data: { ...freeTrial },
729
+ subscription_data: {
730
+ ...freeTrial,
731
+ metadata: {
732
+ ...ctx.body.metadata,
733
+ ...params?.params?.subscription_data?.metadata,
734
+ userId: user$1.id,
735
+ subscriptionId: subscription.id,
736
+ referenceId
737
+ }
738
+ },
436
739
  mode: "subscription",
437
740
  client_reference_id: referenceId,
438
741
  ...params?.params,
439
742
  metadata: {
743
+ ...ctx.body.metadata,
744
+ ...params?.params?.metadata,
440
745
  userId: user$1.id,
441
746
  subscriptionId: subscription.id,
442
- referenceId,
443
- ...params?.params?.metadata
747
+ referenceId
444
748
  }
445
749
  }, params?.options).catch(async (e) => {
446
750
  throw ctx.error("BAD_REQUEST", {
@@ -477,17 +781,19 @@ const cancelSubscriptionCallback = (options) => {
477
781
  value: subscriptionId
478
782
  }]
479
783
  });
480
- if (!subscription || subscription.cancelAtPeriodEnd || subscription.status === "canceled") throw ctx.redirect(getUrl(ctx, callbackURL));
784
+ if (!subscription || subscription.status === "canceled" || isPendingCancel(subscription)) throw ctx.redirect(getUrl(ctx, callbackURL));
481
785
  const currentSubscription = (await client.subscriptions.list({
482
786
  customer: user$1.stripeCustomerId,
483
787
  status: "active"
484
788
  })).data.find((sub) => sub.id === subscription.stripeSubscriptionId);
485
- if (currentSubscription?.cancel_at_period_end === true) {
789
+ if (currentSubscription && isStripePendingCancel(currentSubscription) && !isPendingCancel(subscription)) {
486
790
  await ctx.context.adapter.update({
487
791
  model: "subscription",
488
792
  update: {
489
793
  status: currentSubscription?.status,
490
- cancelAtPeriodEnd: true
794
+ cancelAtPeriodEnd: currentSubscription?.cancel_at_period_end || false,
795
+ cancelAt: currentSubscription?.cancel_at ? /* @__PURE__ */ new Date(currentSubscription.cancel_at * 1e3) : null,
796
+ canceledAt: currentSubscription?.canceled_at ? /* @__PURE__ */ new Date(currentSubscription.canceled_at * 1e3) : null
491
797
  },
492
798
  where: [{
493
799
  field: "id",
@@ -510,7 +816,9 @@ const cancelSubscriptionCallback = (options) => {
510
816
  const cancelSubscriptionBodySchema = z.object({
511
817
  referenceId: z.string().meta({ description: "Reference id of the subscription to cancel. Eg: '123'" }).optional(),
512
818
  subscriptionId: z.string().meta({ description: "The Stripe subscription ID to cancel. Eg: 'sub_1ABC2DEF3GHI4JKL'" }).optional(),
513
- returnUrl: z.string().meta({ description: "URL to take customers to when they click on the billing portal's link to return to your website. Eg: \"/account\"" })
819
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
820
+ returnUrl: z.string().meta({ description: "URL to take customers to when they click on the billing portal's link to return to your website. Eg: \"/account\"" }),
821
+ disableRedirect: z.boolean().meta({ description: "Disable redirect after successful subscription cancellation. Eg: true" }).default(false)
514
822
  });
515
823
  /**
516
824
  * ### Endpoint
@@ -535,12 +843,13 @@ const cancelSubscription = (options) => {
535
843
  body: cancelSubscriptionBodySchema,
536
844
  metadata: { openapi: { operationId: "cancelSubscription" } },
537
845
  use: [
538
- sessionMiddleware,
539
- originCheck((ctx) => ctx.body.returnUrl),
540
- referenceMiddleware(subscriptionOptions, "cancel-subscription")
846
+ stripeSessionMiddleware,
847
+ referenceMiddleware(subscriptionOptions, "cancel-subscription"),
848
+ originCheck((ctx) => ctx.body.returnUrl)
541
849
  ]
542
850
  }, async (ctx) => {
543
- const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
851
+ const customerType = ctx.body.customerType || "user";
852
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
544
853
  let subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
545
854
  model: "subscription",
546
855
  where: [{
@@ -553,10 +862,10 @@ const cancelSubscription = (options) => {
553
862
  field: "referenceId",
554
863
  value: referenceId
555
864
  }]
556
- }).then((subs) => subs.find((sub) => sub.status === "active" || sub.status === "trialing"));
865
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub)));
557
866
  if (ctx.body.subscriptionId && subscription && subscription.referenceId !== referenceId) subscription = void 0;
558
- if (!subscription || !subscription.stripeCustomerId) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
559
- const activeSubscriptions = await client.subscriptions.list({ customer: subscription.stripeCustomerId }).then((res) => res.data.filter((sub) => sub.status === "active" || sub.status === "trialing"));
867
+ if (!subscription || !subscription.stripeCustomerId) throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
868
+ const activeSubscriptions = await client.subscriptions.list({ customer: subscription.stripeCustomerId }).then((res) => res.data.filter((sub) => isActiveOrTrialing(sub)));
560
869
  if (!activeSubscriptions.length) {
561
870
  /**
562
871
  * If the subscription is not found, we need to delete the subscription
@@ -569,10 +878,10 @@ const cancelSubscription = (options) => {
569
878
  value: referenceId
570
879
  }]
571
880
  });
572
- throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
881
+ throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
573
882
  }
574
883
  const activeSubscription = activeSubscriptions.find((sub) => sub.id === subscription.stripeSubscriptionId);
575
- if (!activeSubscription) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
884
+ if (!activeSubscription) throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
576
885
  const { url } = await client.billingPortal.sessions.create({
577
886
  customer: subscription.stripeCustomerId,
578
887
  return_url: getUrl(ctx, `${ctx.context.baseURL}/subscription/cancel/callback?callbackURL=${encodeURIComponent(ctx.body?.returnUrl || "/")}&subscriptionId=${encodeURIComponent(subscription.id)}`),
@@ -581,34 +890,42 @@ const cancelSubscription = (options) => {
581
890
  subscription_cancel: { subscription: activeSubscription.id }
582
891
  }
583
892
  }).catch(async (e) => {
584
- if (e.message.includes("already set to be cancel")) {
893
+ if (e.message?.includes("already set to be canceled")) {
585
894
  /**
586
- * in-case we missed the event from stripe, we set it manually
895
+ * in-case we missed the event from stripe, we sync the actual state
587
896
  * this is a rare case and should not happen
588
897
  */
589
- if (!subscription.cancelAtPeriodEnd) await ctx.context.adapter.updateMany({
590
- model: "subscription",
591
- update: { cancelAtPeriodEnd: true },
592
- where: [{
593
- field: "referenceId",
594
- value: referenceId
595
- }]
596
- });
898
+ if (!isPendingCancel(subscription)) {
899
+ const stripeSub = await client.subscriptions.retrieve(activeSubscription.id);
900
+ await ctx.context.adapter.update({
901
+ model: "subscription",
902
+ update: {
903
+ cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
904
+ cancelAt: stripeSub.cancel_at ? /* @__PURE__ */ new Date(stripeSub.cancel_at * 1e3) : null,
905
+ canceledAt: stripeSub.canceled_at ? /* @__PURE__ */ new Date(stripeSub.canceled_at * 1e3) : null
906
+ },
907
+ where: [{
908
+ field: "id",
909
+ value: subscription.id
910
+ }]
911
+ });
912
+ }
597
913
  }
598
914
  throw ctx.error("BAD_REQUEST", {
599
915
  message: e.message,
600
916
  code: e.code
601
917
  });
602
918
  });
603
- return {
919
+ return ctx.json({
604
920
  url,
605
- redirect: true
606
- };
921
+ redirect: !ctx.body.disableRedirect
922
+ });
607
923
  });
608
924
  };
609
925
  const restoreSubscriptionBodySchema = z.object({
610
926
  referenceId: z.string().meta({ description: "Reference id of the subscription to restore. Eg: '123'" }).optional(),
611
- subscriptionId: z.string().meta({ description: "The Stripe subscription ID to restore. Eg: 'sub_1ABC2DEF3GHI4JKL'" }).optional()
927
+ subscriptionId: z.string().meta({ description: "The Stripe subscription ID to restore. Eg: 'sub_1ABC2DEF3GHI4JKL'" }).optional(),
928
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional()
612
929
  });
613
930
  const restoreSubscription = (options) => {
614
931
  const client = options.stripeClient;
@@ -617,9 +934,10 @@ const restoreSubscription = (options) => {
617
934
  method: "POST",
618
935
  body: restoreSubscriptionBodySchema,
619
936
  metadata: { openapi: { operationId: "restoreSubscription" } },
620
- use: [sessionMiddleware, referenceMiddleware(subscriptionOptions, "restore-subscription")]
937
+ use: [stripeSessionMiddleware, referenceMiddleware(subscriptionOptions, "restore-subscription")]
621
938
  }, async (ctx) => {
622
- const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
939
+ const customerType = ctx.body.customerType || "user";
940
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
623
941
  let subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
624
942
  model: "subscription",
625
943
  where: [{
@@ -632,34 +950,42 @@ const restoreSubscription = (options) => {
632
950
  field: "referenceId",
633
951
  value: referenceId
634
952
  }]
635
- }).then((subs) => subs.find((sub) => sub.status === "active" || sub.status === "trialing"));
953
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub)));
636
954
  if (ctx.body.subscriptionId && subscription && subscription.referenceId !== referenceId) subscription = void 0;
637
- if (!subscription || !subscription.stripeCustomerId) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
638
- if (subscription.status != "active" && subscription.status != "trialing") throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_ACTIVE);
639
- if (!subscription.cancelAtPeriodEnd) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION);
640
- const activeSubscription = await client.subscriptions.list({ customer: subscription.stripeCustomerId }).then((res) => res.data.filter((sub) => sub.status === "active" || sub.status === "trialing")[0]);
641
- if (!activeSubscription) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
642
- try {
643
- const newSub = await client.subscriptions.update(activeSubscription.id, { cancel_at_period_end: false });
644
- await ctx.context.adapter.update({
645
- model: "subscription",
646
- update: {
647
- cancelAtPeriodEnd: false,
648
- updatedAt: /* @__PURE__ */ new Date()
649
- },
650
- where: [{
651
- field: "id",
652
- value: subscription.id
653
- }]
955
+ if (!subscription || !subscription.stripeCustomerId) throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
956
+ if (!isActiveOrTrialing(subscription)) throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE });
957
+ if (!isPendingCancel(subscription)) throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION });
958
+ const activeSubscription = await client.subscriptions.list({ customer: subscription.stripeCustomerId }).then((res) => res.data.filter((sub) => isActiveOrTrialing(sub))[0]);
959
+ if (!activeSubscription) throw ctx.error("BAD_REQUEST", { message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND });
960
+ const updateParams = {};
961
+ if (activeSubscription.cancel_at) updateParams.cancel_at = "";
962
+ else if (activeSubscription.cancel_at_period_end) updateParams.cancel_at_period_end = false;
963
+ const newSub = await client.subscriptions.update(activeSubscription.id, updateParams).catch((e) => {
964
+ throw ctx.error("BAD_REQUEST", {
965
+ message: e.message,
966
+ code: e.code
654
967
  });
655
- return ctx.json(newSub);
656
- } catch (error) {
657
- ctx.context.logger.error("Error restoring subscription", error);
658
- throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.UNABLE_TO_CREATE_CUSTOMER);
659
- }
968
+ });
969
+ await ctx.context.adapter.update({
970
+ model: "subscription",
971
+ update: {
972
+ cancelAtPeriodEnd: false,
973
+ cancelAt: null,
974
+ canceledAt: null,
975
+ updatedAt: /* @__PURE__ */ new Date()
976
+ },
977
+ where: [{
978
+ field: "id",
979
+ value: subscription.id
980
+ }]
981
+ });
982
+ return ctx.json(newSub);
660
983
  });
661
984
  };
662
- const listActiveSubscriptionsQuerySchema = z.optional(z.object({ referenceId: z.string().meta({ description: "Reference id of the subscription to list. Eg: '123'" }).optional() }));
985
+ const listActiveSubscriptionsQuerySchema = z.optional(z.object({
986
+ referenceId: z.string().meta({ description: "Reference id of the subscription to list. Eg: '123'" }).optional(),
987
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional()
988
+ }));
663
989
  /**
664
990
  * ### Endpoint
665
991
  *
@@ -681,13 +1007,15 @@ const listActiveSubscriptions = (options) => {
681
1007
  method: "GET",
682
1008
  query: listActiveSubscriptionsQuerySchema,
683
1009
  metadata: { openapi: { operationId: "listActiveSubscriptions" } },
684
- use: [sessionMiddleware, referenceMiddleware(subscriptionOptions, "list-subscription")]
1010
+ use: [stripeSessionMiddleware, referenceMiddleware(subscriptionOptions, "list-subscription")]
685
1011
  }, async (ctx) => {
1012
+ const customerType = ctx.query?.customerType || "user";
1013
+ const referenceId = ctx.query?.referenceId || getReferenceId(ctx.context.session, customerType, options);
686
1014
  const subscriptions$1 = await ctx.context.adapter.findMany({
687
1015
  model: "subscription",
688
1016
  where: [{
689
1017
  field: "referenceId",
690
- value: ctx.query?.referenceId || ctx.context.session.user.id
1018
+ value: referenceId
691
1019
  }]
692
1020
  });
693
1021
  if (!subscriptions$1.length) return [];
@@ -700,9 +1028,7 @@ const listActiveSubscriptions = (options) => {
700
1028
  limits: plan?.limits,
701
1029
  priceId: plan?.priceId
702
1030
  };
703
- }).filter((sub) => {
704
- return sub.status === "active" || sub.status === "trialing";
705
- });
1031
+ }).filter((sub) => isActiveOrTrialing(sub));
706
1032
  return ctx.json(subs);
707
1033
  });
708
1034
  };
@@ -716,10 +1042,9 @@ const subscriptionSuccess = (options) => {
716
1042
  use: [originCheck((ctx) => ctx.query.callbackURL)]
717
1043
  }, async (ctx) => {
718
1044
  if (!ctx.query || !ctx.query.callbackURL || !ctx.query.subscriptionId) throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
1045
+ const { callbackURL, subscriptionId } = ctx.query;
719
1046
  const session = await getSessionFromCtx(ctx);
720
1047
  if (!session) throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
721
- const { user: user$1 } = session;
722
- const { callbackURL, subscriptionId } = ctx.query;
723
1048
  const subscription = await ctx.context.adapter.findOne({
724
1049
  model: "subscription",
725
1050
  where: [{
@@ -727,38 +1052,53 @@ const subscriptionSuccess = (options) => {
727
1052
  value: subscriptionId
728
1053
  }]
729
1054
  });
730
- if (subscription?.status === "active" || subscription?.status === "trialing") return ctx.redirect(getUrl(ctx, callbackURL));
731
- const customerId = subscription?.stripeCustomerId || user$1.stripeCustomerId;
732
- if (customerId) try {
733
- const stripeSubscription = await client.subscriptions.list({
734
- customer: customerId,
735
- status: "active"
736
- }).then((res) => res.data[0]);
737
- if (stripeSubscription) {
738
- const plan = await getPlanByPriceInfo(options, stripeSubscription.items.data[0]?.price.id, stripeSubscription.items.data[0]?.price.lookup_key);
739
- if (plan && subscription) await ctx.context.adapter.update({
740
- model: "subscription",
741
- update: {
742
- status: stripeSubscription.status,
743
- seats: stripeSubscription.items.data[0]?.quantity || 1,
744
- plan: plan.name.toLowerCase(),
745
- periodEnd: /* @__PURE__ */ new Date(stripeSubscription.items.data[0]?.current_period_end * 1e3),
746
- periodStart: /* @__PURE__ */ new Date(stripeSubscription.items.data[0]?.current_period_start * 1e3),
747
- stripeSubscriptionId: stripeSubscription.id,
748
- ...stripeSubscription.trial_start && stripeSubscription.trial_end ? {
749
- trialStart: /* @__PURE__ */ new Date(stripeSubscription.trial_start * 1e3),
750
- trialEnd: /* @__PURE__ */ new Date(stripeSubscription.trial_end * 1e3)
751
- } : {}
752
- },
753
- where: [{
754
- field: "id",
755
- value: subscription.id
756
- }]
757
- });
758
- }
759
- } catch (error) {
1055
+ if (!subscription) {
1056
+ ctx.context.logger.warn(`Subscription record not found for subscriptionId: ${subscriptionId}`);
1057
+ throw ctx.redirect(getUrl(ctx, callbackURL));
1058
+ }
1059
+ if (isActiveOrTrialing(subscription)) throw ctx.redirect(getUrl(ctx, callbackURL));
1060
+ const customerId = subscription.stripeCustomerId || session.user.stripeCustomerId;
1061
+ if (!customerId) throw ctx.redirect(getUrl(ctx, callbackURL));
1062
+ const stripeSubscription = await client.subscriptions.list({
1063
+ customer: customerId,
1064
+ status: "active"
1065
+ }).then((res) => res.data[0]).catch((error) => {
760
1066
  ctx.context.logger.error("Error fetching subscription from Stripe", error);
1067
+ throw ctx.redirect(getUrl(ctx, callbackURL));
1068
+ });
1069
+ if (!stripeSubscription) throw ctx.redirect(getUrl(ctx, callbackURL));
1070
+ const subscriptionItem = stripeSubscription.items.data[0];
1071
+ if (!subscriptionItem) {
1072
+ ctx.context.logger.warn(`No subscription items found for Stripe subscription ${stripeSubscription.id}`);
1073
+ throw ctx.redirect(getUrl(ctx, callbackURL));
761
1074
  }
1075
+ const plan = await getPlanByPriceInfo(options, subscriptionItem.price.id, subscriptionItem.price.lookup_key);
1076
+ if (!plan) {
1077
+ ctx.context.logger.warn(`Plan not found for price ${subscriptionItem.price.id}`);
1078
+ throw ctx.redirect(getUrl(ctx, callbackURL));
1079
+ }
1080
+ await ctx.context.adapter.update({
1081
+ model: "subscription",
1082
+ update: {
1083
+ status: stripeSubscription.status,
1084
+ seats: subscriptionItem.quantity || 1,
1085
+ plan: plan.name.toLowerCase(),
1086
+ periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
1087
+ periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
1088
+ stripeSubscriptionId: stripeSubscription.id,
1089
+ cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end,
1090
+ cancelAt: stripeSubscription.cancel_at ? /* @__PURE__ */ new Date(stripeSubscription.cancel_at * 1e3) : null,
1091
+ canceledAt: stripeSubscription.canceled_at ? /* @__PURE__ */ new Date(stripeSubscription.canceled_at * 1e3) : null,
1092
+ ...stripeSubscription.trial_start && stripeSubscription.trial_end ? {
1093
+ trialStart: /* @__PURE__ */ new Date(stripeSubscription.trial_start * 1e3),
1094
+ trialEnd: /* @__PURE__ */ new Date(stripeSubscription.trial_end * 1e3)
1095
+ } : {}
1096
+ },
1097
+ where: [{
1098
+ field: "id",
1099
+ value: subscription.id
1100
+ }]
1101
+ });
762
1102
  throw ctx.redirect(getUrl(ctx, callbackURL));
763
1103
  });
764
1104
  };
@@ -767,7 +1107,9 @@ const createBillingPortalBodySchema = z.object({
767
1107
  return typeof localization === "string";
768
1108
  }).optional(),
769
1109
  referenceId: z.string().optional(),
770
- returnUrl: z.string().default("/")
1110
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
1111
+ returnUrl: z.string().default("/"),
1112
+ disableRedirect: z.boolean().meta({ description: "Disable redirect after creating billing portal session. Eg: true" }).default(false)
771
1113
  });
772
1114
  const createBillingPortal = (options) => {
773
1115
  const client = options.stripeClient;
@@ -777,22 +1119,41 @@ const createBillingPortal = (options) => {
777
1119
  body: createBillingPortalBodySchema,
778
1120
  metadata: { openapi: { operationId: "createBillingPortal" } },
779
1121
  use: [
780
- sessionMiddleware,
781
- originCheck((ctx) => ctx.body.returnUrl),
782
- referenceMiddleware(subscriptionOptions, "billing-portal")
1122
+ stripeSessionMiddleware,
1123
+ referenceMiddleware(subscriptionOptions, "billing-portal"),
1124
+ originCheck((ctx) => ctx.body.returnUrl)
783
1125
  ]
784
1126
  }, async (ctx) => {
785
1127
  const { user: user$1 } = ctx.context.session;
786
- const referenceId = ctx.body.referenceId || user$1.id;
787
- let customerId = user$1.stripeCustomerId;
788
- if (!customerId) customerId = (await ctx.context.adapter.findMany({
789
- model: "subscription",
790
- where: [{
791
- field: "referenceId",
792
- value: referenceId
793
- }]
794
- }).then((subs) => subs.find((sub) => sub.status === "active" || sub.status === "trialing")))?.stripeCustomerId;
795
- if (!customerId) throw new APIError("BAD_REQUEST", { message: "No Stripe customer found for this user" });
1128
+ const customerType = ctx.body.customerType || "user";
1129
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
1130
+ let customerId;
1131
+ if (customerType === "organization") {
1132
+ customerId = (await ctx.context.adapter.findOne({
1133
+ model: "organization",
1134
+ where: [{
1135
+ field: "id",
1136
+ value: referenceId
1137
+ }]
1138
+ }))?.stripeCustomerId;
1139
+ if (!customerId) customerId = (await ctx.context.adapter.findMany({
1140
+ model: "subscription",
1141
+ where: [{
1142
+ field: "referenceId",
1143
+ value: referenceId
1144
+ }]
1145
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub))))?.stripeCustomerId;
1146
+ } else {
1147
+ customerId = user$1.stripeCustomerId;
1148
+ if (!customerId) customerId = (await ctx.context.adapter.findMany({
1149
+ model: "subscription",
1150
+ where: [{
1151
+ field: "referenceId",
1152
+ value: referenceId
1153
+ }]
1154
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub))))?.stripeCustomerId;
1155
+ }
1156
+ if (!customerId) throw new APIError$1("NOT_FOUND", { message: STRIPE_ERROR_CODES.CUSTOMER_NOT_FOUND });
796
1157
  try {
797
1158
  const { url } = await client.billingPortal.sessions.create({
798
1159
  locale: ctx.body.locale,
@@ -801,11 +1162,11 @@ const createBillingPortal = (options) => {
801
1162
  });
802
1163
  return ctx.json({
803
1164
  url,
804
- redirect: true
1165
+ redirect: !ctx.body.disableRedirect
805
1166
  });
806
1167
  } catch (error) {
807
1168
  ctx.context.logger.error("Error creating billing portal session", error);
808
- throw new APIError("BAD_REQUEST", { message: error.message });
1169
+ throw new APIError$1("INTERNAL_SERVER_ERROR", { message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_BILLING_PORTAL });
809
1170
  }
810
1171
  });
811
1172
  };
@@ -820,26 +1181,31 @@ const stripeWebhook = (options) => {
820
1181
  cloneRequest: true,
821
1182
  disableBody: true
822
1183
  }, async (ctx) => {
823
- if (!ctx.request?.body) throw new APIError("INTERNAL_SERVER_ERROR");
824
- const buf = await ctx.request.text();
1184
+ if (!ctx.request?.body) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.INVALID_REQUEST_BODY });
825
1185
  const sig = ctx.request.headers.get("stripe-signature");
1186
+ if (!sig) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.STRIPE_SIGNATURE_NOT_FOUND });
826
1187
  const webhookSecret = options.stripeWebhookSecret;
1188
+ if (!webhookSecret) throw new APIError$1("INTERNAL_SERVER_ERROR", { message: STRIPE_ERROR_CODES.STRIPE_WEBHOOK_SECRET_NOT_FOUND });
1189
+ const payload = await ctx.request.text();
827
1190
  let event;
828
1191
  try {
829
- if (!sig || !webhookSecret) throw new APIError("BAD_REQUEST", { message: "Stripe webhook secret not found" });
830
- if (typeof client.webhooks.constructEventAsync === "function") event = await client.webhooks.constructEventAsync(buf, sig, webhookSecret);
831
- else event = client.webhooks.constructEvent(buf, sig, webhookSecret);
1192
+ if (typeof client.webhooks.constructEventAsync === "function") event = await client.webhooks.constructEventAsync(payload, sig, webhookSecret);
1193
+ else event = client.webhooks.constructEvent(payload, sig, webhookSecret);
832
1194
  } catch (err) {
833
1195
  ctx.context.logger.error(`${err.message}`);
834
- throw new APIError("BAD_REQUEST", { message: `Webhook Error: ${err.message}` });
1196
+ throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.FAILED_TO_CONSTRUCT_STRIPE_EVENT });
835
1197
  }
836
- if (!event) throw new APIError("BAD_REQUEST", { message: "Failed to construct event" });
1198
+ if (!event) throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.FAILED_TO_CONSTRUCT_STRIPE_EVENT });
837
1199
  try {
838
1200
  switch (event.type) {
839
1201
  case "checkout.session.completed":
840
1202
  await onCheckoutSessionCompleted(ctx, options, event);
841
1203
  await options.onEvent?.(event);
842
1204
  break;
1205
+ case "customer.subscription.created":
1206
+ await onSubscriptionCreated(ctx, options, event);
1207
+ await options.onEvent?.(event);
1208
+ break;
843
1209
  case "customer.subscription.updated":
844
1210
  await onSubscriptionUpdated(ctx, options, event);
845
1211
  await options.onEvent?.(event);
@@ -854,23 +1220,11 @@ const stripeWebhook = (options) => {
854
1220
  }
855
1221
  } catch (e) {
856
1222
  ctx.context.logger.error(`Stripe webhook failed. Error: ${e.message}`);
857
- throw new APIError("BAD_REQUEST", { message: "Webhook error: See server logs for more information." });
1223
+ throw new APIError$1("BAD_REQUEST", { message: STRIPE_ERROR_CODES.STRIPE_WEBHOOK_ERROR });
858
1224
  }
859
1225
  return ctx.json({ success: true });
860
1226
  });
861
1227
  };
862
- const getUrl = (ctx, url) => {
863
- if (/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(url)) return url;
864
- return `${ctx.context.options.baseURL}${url.startsWith("/") ? url : `/${url}`}`;
865
- };
866
- async function resolvePriceIdFromLookupKey(stripeClient, lookupKey) {
867
- if (!lookupKey) return void 0;
868
- return (await stripeClient.prices.list({
869
- lookup_keys: [lookupKey],
870
- active: true,
871
- limit: 1
872
- })).data[0]?.id;
873
- }
874
1228
 
875
1229
  //#endregion
876
1230
  //#region src/schema.ts
@@ -916,6 +1270,18 @@ const subscriptions = { subscription: { fields: {
916
1270
  required: false,
917
1271
  defaultValue: false
918
1272
  },
1273
+ cancelAt: {
1274
+ type: "date",
1275
+ required: false
1276
+ },
1277
+ canceledAt: {
1278
+ type: "date",
1279
+ required: false
1280
+ },
1281
+ endedAt: {
1282
+ type: "date",
1283
+ required: false
1284
+ },
919
1285
  seats: {
920
1286
  type: "number",
921
1287
  required: false
@@ -925,6 +1291,10 @@ const user = { user: { fields: { stripeCustomerId: {
925
1291
  type: "string",
926
1292
  required: false
927
1293
  } } } };
1294
+ const organization = { organization: { fields: { stripeCustomerId: {
1295
+ type: "string",
1296
+ required: false
1297
+ } } } };
928
1298
  const getSchema = (options) => {
929
1299
  let baseSchema = {};
930
1300
  if (options.subscription?.enabled) baseSchema = {
@@ -932,6 +1302,10 @@ const getSchema = (options) => {
932
1302
  ...user
933
1303
  };
934
1304
  else baseSchema = { ...user };
1305
+ if (options.organization?.enabled) baseSchema = {
1306
+ ...baseSchema,
1307
+ ...organization
1308
+ };
935
1309
  if (options.schema && !options.subscription?.enabled && "subscription" in options.schema) {
936
1310
  const { subscription: _subscription, ...restSchema } = options.schema;
937
1311
  return mergeSchema(baseSchema, restSchema);
@@ -941,16 +1315,6 @@ const getSchema = (options) => {
941
1315
 
942
1316
  //#endregion
943
1317
  //#region src/index.ts
944
- const STRIPE_ERROR_CODES = defineErrorCodes({
945
- SUBSCRIPTION_NOT_FOUND: "Subscription not found",
946
- SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
947
- ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
948
- UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
949
- FAILED_TO_FETCH_PLANS: "Failed to fetch plans",
950
- EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan",
951
- SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
952
- SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION: "Subscription is not scheduled for cancellation"
953
- });
954
1318
  const stripe = (options) => {
955
1319
  const client = options.stripeClient;
956
1320
  const subscriptionEndpoints = {
@@ -969,13 +1333,70 @@ const stripe = (options) => {
969
1333
  ...options.subscription?.enabled ? subscriptionEndpoints : {}
970
1334
  },
971
1335
  init(ctx) {
1336
+ if (options.organization?.enabled) {
1337
+ const orgPlugin = ctx.getPlugin("organization");
1338
+ if (!orgPlugin) {
1339
+ ctx.logger.error(`Organization plugin not found`);
1340
+ return;
1341
+ }
1342
+ const existingHooks = orgPlugin.options.organizationHooks ?? {};
1343
+ /**
1344
+ * Sync organization name to Stripe customer
1345
+ */
1346
+ const afterUpdateStripeOrg = async (data) => {
1347
+ const { organization: organization$1 } = data;
1348
+ if (!organization$1?.stripeCustomerId) return;
1349
+ try {
1350
+ const stripeCustomer = await client.customers.retrieve(organization$1.stripeCustomerId);
1351
+ if (stripeCustomer.deleted) {
1352
+ ctx.logger.warn(`Stripe customer ${organization$1.stripeCustomerId} was deleted`);
1353
+ return;
1354
+ }
1355
+ if (organization$1.name !== stripeCustomer.name) {
1356
+ await client.customers.update(organization$1.stripeCustomerId, { name: organization$1.name });
1357
+ ctx.logger.info(`Synced organization name to Stripe: "${stripeCustomer.name}" → "${organization$1.name}"`);
1358
+ }
1359
+ } catch (e) {
1360
+ ctx.logger.error(`Failed to sync organization to Stripe: ${e.message}`);
1361
+ }
1362
+ };
1363
+ /**
1364
+ * Block deletion if organization has active subscriptions
1365
+ */
1366
+ const beforeDeleteStripeOrg = async (data) => {
1367
+ const { organization: organization$1 } = data;
1368
+ if (!organization$1.stripeCustomerId) return;
1369
+ try {
1370
+ const subscriptions$1 = await client.subscriptions.list({
1371
+ customer: organization$1.stripeCustomerId,
1372
+ status: "all",
1373
+ limit: 100
1374
+ });
1375
+ for (const sub of subscriptions$1.data) if (sub.status !== "canceled" && sub.status !== "incomplete" && sub.status !== "incomplete_expired") throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION);
1376
+ } catch (error) {
1377
+ if (error instanceof APIError) throw error;
1378
+ ctx.logger.error(`Failed to check organization subscriptions: ${error.message}`);
1379
+ throw error;
1380
+ }
1381
+ };
1382
+ orgPlugin.options.organizationHooks = {
1383
+ ...existingHooks,
1384
+ afterUpdateOrganization: existingHooks.afterUpdateOrganization ? async (data) => {
1385
+ await existingHooks.afterUpdateOrganization(data);
1386
+ await afterUpdateStripeOrg(data);
1387
+ } : afterUpdateStripeOrg,
1388
+ beforeDeleteOrganization: existingHooks.beforeDeleteOrganization ? async (data) => {
1389
+ await existingHooks.beforeDeleteOrganization(data);
1390
+ await beforeDeleteStripeOrg(data);
1391
+ } : beforeDeleteStripeOrg
1392
+ };
1393
+ }
972
1394
  return { options: { databaseHooks: { user: {
973
1395
  create: { async after(user$1, ctx$1) {
974
- if (!ctx$1 || !options.createCustomerOnSignUp) return;
1396
+ if (!ctx$1 || !options.createCustomerOnSignUp || user$1.stripeCustomerId) return;
975
1397
  try {
976
- if (user$1.stripeCustomerId) return;
977
- let stripeCustomer = (await client.customers.list({
978
- email: user$1.email,
1398
+ let stripeCustomer = (await client.customers.search({
1399
+ query: `email:"${escapeStripeSearchValue(user$1.email)}" AND -metadata["customerType"]:"organization"`,
979
1400
  limit: 1
980
1401
  })).data[0];
981
1402
  if (stripeCustomer) {
@@ -995,7 +1416,10 @@ const stripe = (options) => {
995
1416
  const params = defu({
996
1417
  email: user$1.email,
997
1418
  name: user$1.name,
998
- metadata: { userId: user$1.id }
1419
+ metadata: {
1420
+ userId: user$1.id,
1421
+ customerType: "user"
1422
+ }
999
1423
  }, extraCreateParams);
1000
1424
  stripeCustomer = await client.customers.create(params);
1001
1425
  await ctx$1.context.internalAdapter.updateUser(user$1.id, { stripeCustomerId: stripeCustomer.id });
@@ -1012,17 +1436,15 @@ const stripe = (options) => {
1012
1436
  }
1013
1437
  } },
1014
1438
  update: { async after(user$1, ctx$1) {
1015
- if (!ctx$1) return;
1439
+ if (!ctx$1 || !user$1.stripeCustomerId) return;
1016
1440
  try {
1017
- const userWithStripe = user$1;
1018
- if (!userWithStripe.stripeCustomerId) return;
1019
- const stripeCustomer = await client.customers.retrieve(userWithStripe.stripeCustomerId);
1441
+ const stripeCustomer = await client.customers.retrieve(user$1.stripeCustomerId);
1020
1442
  if (stripeCustomer.deleted) {
1021
- ctx$1.context.logger.warn(`Stripe customer ${userWithStripe.stripeCustomerId} was deleted, cannot update email`);
1443
+ ctx$1.context.logger.warn(`Stripe customer ${user$1.stripeCustomerId} was deleted, cannot update email`);
1022
1444
  return;
1023
1445
  }
1024
1446
  if (stripeCustomer.email !== user$1.email) {
1025
- await client.customers.update(userWithStripe.stripeCustomerId, { email: user$1.email });
1447
+ await client.customers.update(user$1.stripeCustomerId, { email: user$1.email });
1026
1448
  ctx$1.context.logger.info(`Updated Stripe customer email from ${stripeCustomer.email} to ${user$1.email}`);
1027
1449
  }
1028
1450
  } catch (e) {