@better-auth/stripe 1.5.0-beta.1 → 1.5.0-beta.3

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,10 +1,9 @@
1
- import { t as STRIPE_ERROR_CODES$1 } from "./error-codes-qqooUh6R.mjs";
2
- import { defineErrorCodes } from "@better-auth/core/utils";
1
+ import { t as STRIPE_ERROR_CODES } from "./error-codes-Bkj5yJMT.mjs";
2
+ import { APIError, HIDE_METADATA } from "better-auth";
3
3
  import { defu } from "defu";
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
- import { APIError as APIError$1, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
5
+ import { APIError as APIError$1 } from "@better-auth/core/error";
6
+ import { getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
8
7
  import * as z from "zod/v4";
9
8
  import { mergeSchema } from "better-auth/db";
10
9
 
@@ -19,21 +18,86 @@ async function getPlanByPriceInfo(options, priceId, priceLookupKey) {
19
18
  async function getPlanByName(options, name) {
20
19
  return await getPlans(options.subscription).then((res) => res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()));
21
20
  }
21
+ /**
22
+ * Checks if a subscription is in an available state (active or trialing)
23
+ */
24
+ function isActiveOrTrialing(sub) {
25
+ return sub.status === "active" || sub.status === "trialing";
26
+ }
27
+ /**
28
+ * Check if a subscription is scheduled to be canceled (DB subscription object)
29
+ */
30
+ function isPendingCancel(sub) {
31
+ return !!(sub.cancelAtPeriodEnd || sub.cancelAt);
32
+ }
33
+ /**
34
+ * Check if a Stripe subscription is scheduled to be canceled (Stripe API response)
35
+ */
36
+ function isStripePendingCancel(stripeSub) {
37
+ return !!(stripeSub.cancel_at_period_end || stripeSub.cancel_at);
38
+ }
39
+ /**
40
+ * Escapes a value for use in Stripe search queries.
41
+ * Stripe search query uses double quotes for string values,
42
+ * and double quotes within the value need to be escaped with backslash.
43
+ *
44
+ * @see https://docs.stripe.com/search#search-query-language
45
+ */
46
+ function escapeStripeSearchValue(value) {
47
+ return value.replace(/"/g, "\\\"");
48
+ }
22
49
 
23
50
  //#endregion
24
51
  //#region src/hooks.ts
52
+ /**
53
+ * Find organization or user by stripeCustomerId.
54
+ * @internal
55
+ */
56
+ async function findReferenceByStripeCustomerId(ctx, options, stripeCustomerId) {
57
+ if (options.organization?.enabled) {
58
+ const org = await ctx.context.adapter.findOne({
59
+ model: "organization",
60
+ where: [{
61
+ field: "stripeCustomerId",
62
+ value: stripeCustomerId
63
+ }]
64
+ });
65
+ if (org) return {
66
+ customerType: "organization",
67
+ referenceId: org.id
68
+ };
69
+ }
70
+ const user$1 = await ctx.context.adapter.findOne({
71
+ model: "user",
72
+ where: [{
73
+ field: "stripeCustomerId",
74
+ value: stripeCustomerId
75
+ }]
76
+ });
77
+ if (user$1) return {
78
+ customerType: "user",
79
+ referenceId: user$1.id
80
+ };
81
+ return null;
82
+ }
25
83
  async function onCheckoutSessionCompleted(ctx, options, event) {
26
84
  try {
27
85
  const client = options.stripeClient;
28
86
  const checkoutSession = event.data.object;
29
87
  if (checkoutSession.mode === "setup" || !options.subscription?.enabled) return;
30
88
  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);
89
+ const subscriptionItem = subscription.items.data[0];
90
+ if (!subscriptionItem) {
91
+ ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscription.id} has no items`);
92
+ return;
93
+ }
94
+ const priceId = subscriptionItem.price.id;
95
+ const priceLookupKey = subscriptionItem.price.lookup_key;
96
+ const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
33
97
  if (plan) {
34
98
  const referenceId = checkoutSession?.client_reference_id || checkoutSession?.metadata?.referenceId;
35
99
  const subscriptionId = checkoutSession?.metadata?.subscriptionId;
36
- const seats = subscription.items.data[0].quantity;
100
+ const seats = subscriptionItem.quantity;
37
101
  if (referenceId && subscriptionId) {
38
102
  const trial = subscription.trial_start && subscription.trial_end ? {
39
103
  trialStart: /* @__PURE__ */ new Date(subscription.trial_start * 1e3),
@@ -45,9 +109,13 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
45
109
  plan: plan.name.toLowerCase(),
46
110
  status: subscription.status,
47
111
  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),
112
+ periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
113
+ periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
50
114
  stripeSubscriptionId: checkoutSession.subscription,
115
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
116
+ cancelAt: subscription.cancel_at ? /* @__PURE__ */ new Date(subscription.cancel_at * 1e3) : null,
117
+ canceledAt: subscription.canceled_at ? /* @__PURE__ */ new Date(subscription.canceled_at * 1e3) : null,
118
+ endedAt: subscription.ended_at ? /* @__PURE__ */ new Date(subscription.ended_at * 1e3) : null,
51
119
  seats,
52
120
  ...trial
53
121
  },
@@ -74,15 +142,95 @@ async function onCheckoutSessionCompleted(ctx, options, event) {
74
142
  }
75
143
  }
76
144
  } catch (e) {
77
- logger.error(`Stripe webhook failed. Error: ${e.message}`);
145
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${e.message}`);
146
+ }
147
+ }
148
+ async function onSubscriptionCreated(ctx, options, event) {
149
+ try {
150
+ if (!options.subscription?.enabled) return;
151
+ const subscriptionCreated = event.data.object;
152
+ const stripeCustomerId = subscriptionCreated.customer?.toString();
153
+ if (!stripeCustomerId) {
154
+ ctx.context.logger.warn(`Stripe webhook warning: customer.subscription.created event received without customer ID`);
155
+ return;
156
+ }
157
+ const subscriptionId = subscriptionCreated.metadata?.subscriptionId;
158
+ const existingSubscription = await ctx.context.adapter.findOne({
159
+ model: "subscription",
160
+ where: subscriptionId ? [{
161
+ field: "id",
162
+ value: subscriptionId
163
+ }] : [{
164
+ field: "stripeSubscriptionId",
165
+ value: subscriptionCreated.id
166
+ }]
167
+ });
168
+ if (existingSubscription) {
169
+ ctx.context.logger.info(`Stripe webhook: Subscription already exists in database (id: ${existingSubscription.id}), skipping creation`);
170
+ return;
171
+ }
172
+ const reference = await findReferenceByStripeCustomerId(ctx, options, stripeCustomerId);
173
+ if (!reference) {
174
+ ctx.context.logger.warn(`Stripe webhook warning: No user or organization found with stripeCustomerId: ${stripeCustomerId}`);
175
+ return;
176
+ }
177
+ const { referenceId, customerType } = reference;
178
+ const subscriptionItem = subscriptionCreated.items.data[0];
179
+ if (!subscriptionItem) {
180
+ ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscriptionCreated.id} has no items`);
181
+ return;
182
+ }
183
+ const priceId = subscriptionItem.price.id;
184
+ const plan = await getPlanByPriceInfo(options, priceId, subscriptionItem.price.lookup_key || null);
185
+ if (!plan) {
186
+ ctx.context.logger.warn(`Stripe webhook warning: No matching plan found for priceId: ${priceId}`);
187
+ return;
188
+ }
189
+ const seats = subscriptionItem.quantity;
190
+ const periodStart = /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3);
191
+ const periodEnd = /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3);
192
+ const trial = subscriptionCreated.trial_start && subscriptionCreated.trial_end ? {
193
+ trialStart: /* @__PURE__ */ new Date(subscriptionCreated.trial_start * 1e3),
194
+ trialEnd: /* @__PURE__ */ new Date(subscriptionCreated.trial_end * 1e3)
195
+ } : {};
196
+ const newSubscription = await ctx.context.adapter.create({
197
+ model: "subscription",
198
+ data: {
199
+ referenceId,
200
+ stripeCustomerId,
201
+ stripeSubscriptionId: subscriptionCreated.id,
202
+ status: subscriptionCreated.status,
203
+ plan: plan.name.toLowerCase(),
204
+ periodStart,
205
+ periodEnd,
206
+ seats,
207
+ ...plan.limits ? { limits: plan.limits } : {},
208
+ ...trial
209
+ }
210
+ });
211
+ ctx.context.logger.info(`Stripe webhook: Created subscription ${subscriptionCreated.id} for ${customerType} ${referenceId} from dashboard`);
212
+ await options.subscription.onSubscriptionCreated?.({
213
+ event,
214
+ subscription: newSubscription,
215
+ stripeSubscription: subscriptionCreated,
216
+ plan
217
+ });
218
+ } catch (error) {
219
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
78
220
  }
79
221
  }
80
222
  async function onSubscriptionUpdated(ctx, options, event) {
81
223
  try {
82
224
  if (!options.subscription?.enabled) return;
83
225
  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);
226
+ const subscriptionItem = subscriptionUpdated.items.data[0];
227
+ if (!subscriptionItem) {
228
+ ctx.context.logger.warn(`Stripe webhook warning: Subscription ${subscriptionUpdated.id} has no items`);
229
+ return;
230
+ }
231
+ const priceId = subscriptionItem.price.id;
232
+ const priceLookupKey = subscriptionItem.price.lookup_key;
233
+ const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
86
234
  const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
87
235
  const customerId = subscriptionUpdated.customer?.toString();
88
236
  let subscription = await ctx.context.adapter.findOne({
@@ -104,15 +252,14 @@ async function onSubscriptionUpdated(ctx, options, event) {
104
252
  }]
105
253
  });
106
254
  if (subs.length > 1) {
107
- const activeSub = subs.find((sub) => sub.status === "active" || sub.status === "trialing");
255
+ const activeSub = subs.find((sub) => isActiveOrTrialing(sub));
108
256
  if (!activeSub) {
109
- logger.warn(`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no active subscription is found`);
257
+ ctx.context.logger.warn(`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no active subscription is found`);
110
258
  return;
111
259
  }
112
260
  subscription = activeSub;
113
261
  } else subscription = subs[0];
114
262
  }
115
- const seats = subscriptionUpdated.items.data[0].quantity;
116
263
  const updatedSubscription = await ctx.context.adapter.update({
117
264
  model: "subscription",
118
265
  update: {
@@ -122,10 +269,13 @@ async function onSubscriptionUpdated(ctx, options, event) {
122
269
  } : {},
123
270
  updatedAt: /* @__PURE__ */ new Date(),
124
271
  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),
272
+ periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
273
+ periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
127
274
  cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
128
- seats,
275
+ cancelAt: subscriptionUpdated.cancel_at ? /* @__PURE__ */ new Date(subscriptionUpdated.cancel_at * 1e3) : null,
276
+ canceledAt: subscriptionUpdated.canceled_at ? /* @__PURE__ */ new Date(subscriptionUpdated.canceled_at * 1e3) : null,
277
+ endedAt: subscriptionUpdated.ended_at ? /* @__PURE__ */ new Date(subscriptionUpdated.ended_at * 1e3) : null,
278
+ seats: subscriptionItem.quantity,
129
279
  stripeSubscriptionId: subscriptionUpdated.id
130
280
  },
131
281
  where: [{
@@ -133,7 +283,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
133
283
  value: subscription.id
134
284
  }]
135
285
  });
136
- if (subscriptionUpdated.status === "active" && subscriptionUpdated.cancel_at_period_end && !subscription.cancelAtPeriodEnd) await options.subscription.onSubscriptionCancel?.({
286
+ if (subscriptionUpdated.status === "active" && isStripePendingCancel(subscriptionUpdated) && !isPendingCancel(subscription)) await options.subscription.onSubscriptionCancel?.({
137
287
  subscription,
138
288
  cancellationDetails: subscriptionUpdated.cancellation_details || void 0,
139
289
  stripeSubscription: subscriptionUpdated,
@@ -148,7 +298,7 @@ async function onSubscriptionUpdated(ctx, options, event) {
148
298
  if (subscriptionUpdated.status === "incomplete_expired" && subscription.status === "trialing" && plan.freeTrial?.onTrialExpired) await plan.freeTrial.onTrialExpired(subscription, ctx);
149
299
  }
150
300
  } catch (error) {
151
- logger.error(`Stripe webhook failed. Error: ${error}`);
301
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
152
302
  }
153
303
  }
154
304
  async function onSubscriptionDeleted(ctx, options, event) {
@@ -172,7 +322,11 @@ async function onSubscriptionDeleted(ctx, options, event) {
172
322
  }],
173
323
  update: {
174
324
  status: "canceled",
175
- updatedAt: /* @__PURE__ */ new Date()
325
+ updatedAt: /* @__PURE__ */ new Date(),
326
+ cancelAtPeriodEnd: subscriptionDeleted.cancel_at_period_end,
327
+ cancelAt: subscriptionDeleted.cancel_at ? /* @__PURE__ */ new Date(subscriptionDeleted.cancel_at * 1e3) : null,
328
+ canceledAt: subscriptionDeleted.canceled_at ? /* @__PURE__ */ new Date(subscriptionDeleted.canceled_at * 1e3) : null,
329
+ endedAt: subscriptionDeleted.ended_at ? /* @__PURE__ */ new Date(subscriptionDeleted.ended_at * 1e3) : null
176
330
  }
177
331
  });
178
332
  await options.subscription.onSubscriptionDeleted?.({
@@ -180,41 +334,94 @@ async function onSubscriptionDeleted(ctx, options, event) {
180
334
  stripeSubscription: subscriptionDeleted,
181
335
  subscription
182
336
  });
183
- } else logger.warn(`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`);
337
+ } else ctx.context.logger.warn(`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`);
184
338
  } catch (error) {
185
- logger.error(`Stripe webhook failed. Error: ${error}`);
339
+ ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
186
340
  }
187
341
  }
188
342
 
189
343
  //#endregion
190
344
  //#region src/middleware.ts
345
+ const stripeSessionMiddleware = createAuthMiddleware({ use: [sessionMiddleware] }, async (ctx) => {
346
+ return { session: ctx.context.session };
347
+ });
191
348
  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." });
349
+ const ctxSession = ctx.context.session;
350
+ if (!ctxSession) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
351
+ const customerType = ctx.body?.customerType || ctx.query?.customerType;
352
+ const explicitReferenceId = ctx.body?.referenceId || ctx.query?.referenceId;
353
+ if (customerType === "organization") {
354
+ if (!subscriptionOptions.authorizeReference) {
355
+ ctx.context.logger.error(`Organization subscriptions require authorizeReference to be defined in your stripe plugin config.`);
356
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_SUBSCRIPTION_NOT_ENABLED);
357
+ }
358
+ const referenceId = explicitReferenceId || ctxSession.session.activeOrganizationId;
359
+ if (!referenceId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_REFERENCE_ID_REQUIRED);
360
+ if (!await subscriptionOptions.authorizeReference({
361
+ user: ctxSession.user,
362
+ session: ctxSession.session,
363
+ referenceId,
364
+ action
365
+ }, ctx)) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
366
+ return;
367
+ }
368
+ if (!explicitReferenceId) return;
369
+ if (explicitReferenceId === ctxSession.user.id) return;
370
+ if (!subscriptionOptions.authorizeReference) {
371
+ ctx.context.logger.error(`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`);
372
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.REFERENCE_ID_NOT_ALLOWED);
198
373
  }
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,
374
+ if (!await subscriptionOptions.authorizeReference({
375
+ user: ctxSession.user,
376
+ session: ctxSession.session,
377
+ referenceId: explicitReferenceId,
207
378
  action
208
- }, ctx) || sameReference : true)) throw new APIError$1("UNAUTHORIZED", { message: "Unauthorized" });
379
+ }, ctx)) throw APIError$1.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
209
380
  });
210
381
 
211
382
  //#endregion
212
383
  //#region src/routes.ts
384
+ /**
385
+ * Converts a relative URL to an absolute URL using baseURL.
386
+ * @internal
387
+ */
388
+ function getUrl(ctx, url) {
389
+ if (/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(url)) return url;
390
+ return `${ctx.context.options.baseURL}${url.startsWith("/") ? url : `/${url}`}`;
391
+ }
392
+ /**
393
+ * Resolves a Stripe price ID from a lookup key.
394
+ * @internal
395
+ */
396
+ async function resolvePriceIdFromLookupKey(stripeClient, lookupKey) {
397
+ if (!lookupKey) return void 0;
398
+ return (await stripeClient.prices.list({
399
+ lookup_keys: [lookupKey],
400
+ active: true,
401
+ limit: 1
402
+ })).data[0]?.id;
403
+ }
404
+ /**
405
+ * Determines the reference ID based on customer type.
406
+ * - `user` (default): uses userId
407
+ * - `organization`: uses activeOrganizationId from session
408
+ * @internal
409
+ */
410
+ function getReferenceId(ctxSession, customerType, options) {
411
+ const { user: user$1, session } = ctxSession;
412
+ if ((customerType || "user") === "organization") {
413
+ if (!options.organization?.enabled) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_SUBSCRIPTION_NOT_ENABLED);
414
+ if (!session.activeOrganizationId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_NOT_FOUND);
415
+ return session.activeOrganizationId;
416
+ }
417
+ return user$1.id;
418
+ }
213
419
  const upgradeSubscriptionBodySchema = z.object({
214
420
  plan: z.string().meta({ description: "The name of the plan to upgrade to. Eg: \"pro\"" }),
215
421
  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(),
422
+ referenceId: z.string().meta({ description: "Reference ID for the subscription. Eg: \"org_123\"" }).optional(),
217
423
  subscriptionId: z.string().meta({ description: "The Stripe subscription ID to upgrade. Eg: \"sub_1ABC2DEF3GHI4JKL\"" }).optional(),
424
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
218
425
  metadata: z.record(z.string(), z.any()).optional(),
219
426
  seats: z.number().meta({ description: "Number of seats to upgrade to (if applicable). Eg: 1" }).optional(),
220
427
  successUrl: z.string().meta({ description: "Callback URL to redirect back after successful subscription. Eg: \"https://example.com/success\"" }).default("/"),
@@ -245,18 +452,19 @@ const upgradeSubscription = (options) => {
245
452
  body: upgradeSubscriptionBodySchema,
246
453
  metadata: { openapi: { operationId: "upgradeSubscription" } },
247
454
  use: [
248
- sessionMiddleware,
455
+ stripeSessionMiddleware,
456
+ referenceMiddleware(subscriptionOptions, "upgrade-subscription"),
249
457
  originCheck((c) => {
250
458
  return [c.body.successUrl, c.body.cancelUrl];
251
- }),
252
- referenceMiddleware(subscriptionOptions, "upgrade-subscription")
459
+ })
253
460
  ]
254
461
  }, async (ctx) => {
255
462
  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;
463
+ const customerType = ctx.body.customerType || "user";
464
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
465
+ if (!user$1.emailVerified && subscriptionOptions.requireEmailVerification) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED);
258
466
  const plan = await getPlanByName(options, ctx.body.plan);
259
- if (!plan) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_PLAN_NOT_FOUND);
467
+ if (!plan) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND);
260
468
  let subscriptionToUpdate = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
261
469
  model: "subscription",
262
470
  where: [{
@@ -271,49 +479,104 @@ const upgradeSubscription = (options) => {
271
479
  }]
272
480
  }) : null;
273
481
  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
482
+ if (ctx.body.subscriptionId && !subscriptionToUpdate) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
483
+ let customerId;
484
+ if (customerType === "organization") {
485
+ customerId = subscriptionToUpdate?.stripeCustomerId;
486
+ if (!customerId) {
487
+ const org = await ctx.context.adapter.findOne({
488
+ model: "organization",
489
+ where: [{
490
+ field: "id",
491
+ value: referenceId
492
+ }]
493
+ });
494
+ if (!org) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ORGANIZATION_NOT_FOUND);
495
+ customerId = org.stripeCustomerId;
496
+ if (!customerId) try {
497
+ let stripeCustomer = (await client.customers.search({
498
+ query: `metadata["organizationId"]:"${org.id}"`,
499
+ limit: 1
500
+ })).data[0];
501
+ if (!stripeCustomer) {
502
+ let extraCreateParams = {};
503
+ if (options.organization?.getCustomerCreateParams) extraCreateParams = await options.organization.getCustomerCreateParams(org, ctx);
504
+ const customerParams = defu({
505
+ name: org.name,
506
+ metadata: {
507
+ ...ctx.body.metadata,
508
+ organizationId: org.id,
509
+ customerType: "organization"
510
+ }
511
+ }, extraCreateParams);
512
+ stripeCustomer = await client.customers.create(customerParams);
513
+ await options.organization?.onCustomerCreate?.({
514
+ stripeCustomer,
515
+ organization: {
516
+ ...org,
517
+ stripeCustomerId: stripeCustomer.id
518
+ }
519
+ }, ctx);
520
+ }
521
+ await ctx.context.adapter.update({
522
+ model: "organization",
523
+ update: { stripeCustomerId: stripeCustomer.id },
524
+ where: [{
525
+ field: "id",
526
+ value: org.id
527
+ }]
528
+ });
529
+ customerId = stripeCustomer.id;
530
+ } catch (e) {
531
+ ctx.context.logger.error(e);
532
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER);
287
533
  }
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);
534
+ }
535
+ } else {
536
+ customerId = subscriptionToUpdate?.stripeCustomerId || user$1.stripeCustomerId;
537
+ if (!customerId) try {
538
+ let stripeCustomer = (await client.customers.search({
539
+ query: `email:"${escapeStripeSearchValue(user$1.email)}" AND -metadata["customerType"]:"organization"`,
540
+ limit: 1
541
+ })).data[0];
542
+ if (!stripeCustomer) stripeCustomer = await client.customers.create({
543
+ email: user$1.email,
544
+ name: user$1.name,
545
+ metadata: {
546
+ ...ctx.body.metadata,
547
+ userId: user$1.id,
548
+ customerType: "user"
549
+ }
550
+ });
551
+ await ctx.context.adapter.update({
552
+ model: "user",
553
+ update: { stripeCustomerId: stripeCustomer.id },
554
+ where: [{
555
+ field: "id",
556
+ value: user$1.id
557
+ }]
558
+ });
559
+ customerId = stripeCustomer.id;
560
+ } catch (e) {
561
+ ctx.context.logger.error(e);
562
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER);
563
+ }
301
564
  }
302
565
  const subscriptions$1 = subscriptionToUpdate ? [subscriptionToUpdate] : await ctx.context.adapter.findMany({
303
566
  model: "subscription",
304
567
  where: [{
305
568
  field: "referenceId",
306
- value: ctx.body.referenceId || user$1.id
569
+ value: referenceId
307
570
  }]
308
571
  });
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) => {
572
+ const activeOrTrialingSubscription = subscriptions$1.find((sub) => isActiveOrTrialing(sub));
573
+ const activeSubscription = (await client.subscriptions.list({ customer: customerId }).then((res) => res.data.filter((sub) => isActiveOrTrialing(sub)))).find((sub) => {
311
574
  if (subscriptionToUpdate?.stripeSubscriptionId || ctx.body.subscriptionId) return sub.id === subscriptionToUpdate?.stripeSubscriptionId || sub.id === ctx.body.subscriptionId;
312
575
  if (activeOrTrialingSubscription?.stripeSubscriptionId) return sub.id === activeOrTrialingSubscription.stripeSubscriptionId;
313
576
  return false;
314
577
  });
315
578
  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);
579
+ if (activeOrTrialingSubscription && activeOrTrialingSubscription.status === "active" && activeOrTrialingSubscription.plan === ctx.body.plan && activeOrTrialingSubscription.seats === (ctx.body.seats || 1)) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN);
317
580
  if (activeSubscription && customerId) {
318
581
  let dbSubscription = await ctx.context.adapter.findOne({
319
582
  model: "subscription",
@@ -371,7 +634,7 @@ const upgradeSubscription = (options) => {
371
634
  });
372
635
  return ctx.json({
373
636
  url,
374
- redirect: true
637
+ redirect: !ctx.body.disableRedirect
375
638
  });
376
639
  }
377
640
  let subscription = activeOrTrialingSubscription || incompleteSubscription;
@@ -399,7 +662,7 @@ const upgradeSubscription = (options) => {
399
662
  });
400
663
  if (!subscription) {
401
664
  ctx.context.logger.error("Subscription ID not found");
402
- throw new APIError("INTERNAL_SERVER_ERROR");
665
+ throw APIError$1.from("NOT_FOUND", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
403
666
  }
404
667
  const params = await subscriptionOptions.getCheckoutSessionParams?.({
405
668
  user: user$1,
@@ -407,7 +670,13 @@ const upgradeSubscription = (options) => {
407
670
  plan,
408
671
  subscription
409
672
  }, ctx.request, ctx);
410
- const freeTrial = !subscriptions$1.some((s) => {
673
+ const freeTrial = !(await ctx.context.adapter.findMany({
674
+ model: "subscription",
675
+ where: [{
676
+ field: "referenceId",
677
+ value: referenceId
678
+ }]
679
+ })).some((s) => {
411
680
  return !!(s.trialStart || s.trialEnd) || s.status === "trialing";
412
681
  }) && plan.freeTrial ? { trial_period_days: plan.freeTrial.days } : void 0;
413
682
  let priceIdToUse = void 0;
@@ -421,26 +690,36 @@ const upgradeSubscription = (options) => {
421
690
  const checkoutSession = await client.checkout.sessions.create({
422
691
  ...customerId ? {
423
692
  customer: customerId,
424
- customer_update: {
693
+ customer_update: customerType !== "user" ? { address: "auto" } : {
425
694
  name: "auto",
426
695
  address: "auto"
427
696
  }
428
- } : { customer_email: session.user.email },
697
+ } : { customer_email: user$1.email },
429
698
  success_url: getUrl(ctx, `${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(ctx.body.successUrl)}&subscriptionId=${encodeURIComponent(subscription.id)}`),
430
699
  cancel_url: getUrl(ctx, ctx.body.cancelUrl),
431
700
  line_items: [{
432
701
  price: priceIdToUse,
433
702
  quantity: ctx.body.seats || 1
434
703
  }],
435
- subscription_data: { ...freeTrial },
704
+ subscription_data: {
705
+ ...freeTrial,
706
+ metadata: {
707
+ ...ctx.body.metadata,
708
+ ...params?.params?.subscription_data?.metadata,
709
+ userId: user$1.id,
710
+ subscriptionId: subscription.id,
711
+ referenceId
712
+ }
713
+ },
436
714
  mode: "subscription",
437
715
  client_reference_id: referenceId,
438
716
  ...params?.params,
439
717
  metadata: {
718
+ ...ctx.body.metadata,
719
+ ...params?.params?.metadata,
440
720
  userId: user$1.id,
441
721
  subscriptionId: subscription.id,
442
- referenceId,
443
- ...params?.params?.metadata
722
+ referenceId
444
723
  }
445
724
  }, params?.options).catch(async (e) => {
446
725
  throw ctx.error("BAD_REQUEST", {
@@ -477,17 +756,19 @@ const cancelSubscriptionCallback = (options) => {
477
756
  value: subscriptionId
478
757
  }]
479
758
  });
480
- if (!subscription || subscription.cancelAtPeriodEnd || subscription.status === "canceled") throw ctx.redirect(getUrl(ctx, callbackURL));
759
+ if (!subscription || subscription.status === "canceled" || isPendingCancel(subscription)) throw ctx.redirect(getUrl(ctx, callbackURL));
481
760
  const currentSubscription = (await client.subscriptions.list({
482
761
  customer: user$1.stripeCustomerId,
483
762
  status: "active"
484
763
  })).data.find((sub) => sub.id === subscription.stripeSubscriptionId);
485
- if (currentSubscription?.cancel_at_period_end === true) {
764
+ if (currentSubscription && isStripePendingCancel(currentSubscription) && !isPendingCancel(subscription)) {
486
765
  await ctx.context.adapter.update({
487
766
  model: "subscription",
488
767
  update: {
489
768
  status: currentSubscription?.status,
490
- cancelAtPeriodEnd: true
769
+ cancelAtPeriodEnd: currentSubscription?.cancel_at_period_end || false,
770
+ cancelAt: currentSubscription?.cancel_at ? /* @__PURE__ */ new Date(currentSubscription.cancel_at * 1e3) : null,
771
+ canceledAt: currentSubscription?.canceled_at ? /* @__PURE__ */ new Date(currentSubscription.canceled_at * 1e3) : null
491
772
  },
492
773
  where: [{
493
774
  field: "id",
@@ -510,7 +791,9 @@ const cancelSubscriptionCallback = (options) => {
510
791
  const cancelSubscriptionBodySchema = z.object({
511
792
  referenceId: z.string().meta({ description: "Reference id of the subscription to cancel. Eg: '123'" }).optional(),
512
793
  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\"" })
794
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
795
+ 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\"" }),
796
+ disableRedirect: z.boolean().meta({ description: "Disable redirect after successful subscription cancellation. Eg: true" }).default(false)
514
797
  });
515
798
  /**
516
799
  * ### Endpoint
@@ -535,12 +818,13 @@ const cancelSubscription = (options) => {
535
818
  body: cancelSubscriptionBodySchema,
536
819
  metadata: { openapi: { operationId: "cancelSubscription" } },
537
820
  use: [
538
- sessionMiddleware,
539
- originCheck((ctx) => ctx.body.returnUrl),
540
- referenceMiddleware(subscriptionOptions, "cancel-subscription")
821
+ stripeSessionMiddleware,
822
+ referenceMiddleware(subscriptionOptions, "cancel-subscription"),
823
+ originCheck((ctx) => ctx.body.returnUrl)
541
824
  ]
542
825
  }, async (ctx) => {
543
- const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
826
+ const customerType = ctx.body.customerType || "user";
827
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
544
828
  let subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
545
829
  model: "subscription",
546
830
  where: [{
@@ -553,10 +837,10 @@ const cancelSubscription = (options) => {
553
837
  field: "referenceId",
554
838
  value: referenceId
555
839
  }]
556
- }).then((subs) => subs.find((sub) => sub.status === "active" || sub.status === "trialing"));
840
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub)));
557
841
  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"));
842
+ if (!subscription || !subscription.stripeCustomerId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
843
+ const activeSubscriptions = await client.subscriptions.list({ customer: subscription.stripeCustomerId }).then((res) => res.data.filter((sub) => isActiveOrTrialing(sub)));
560
844
  if (!activeSubscriptions.length) {
561
845
  /**
562
846
  * If the subscription is not found, we need to delete the subscription
@@ -569,10 +853,10 @@ const cancelSubscription = (options) => {
569
853
  value: referenceId
570
854
  }]
571
855
  });
572
- throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
856
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
573
857
  }
574
858
  const activeSubscription = activeSubscriptions.find((sub) => sub.id === subscription.stripeSubscriptionId);
575
- if (!activeSubscription) throw APIError.from("BAD_REQUEST", STRIPE_ERROR_CODES$1.SUBSCRIPTION_NOT_FOUND);
859
+ if (!activeSubscription) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
576
860
  const { url } = await client.billingPortal.sessions.create({
577
861
  customer: subscription.stripeCustomerId,
578
862
  return_url: getUrl(ctx, `${ctx.context.baseURL}/subscription/cancel/callback?callbackURL=${encodeURIComponent(ctx.body?.returnUrl || "/")}&subscriptionId=${encodeURIComponent(subscription.id)}`),
@@ -581,34 +865,42 @@ const cancelSubscription = (options) => {
581
865
  subscription_cancel: { subscription: activeSubscription.id }
582
866
  }
583
867
  }).catch(async (e) => {
584
- if (e.message.includes("already set to be cancel")) {
868
+ if (e.message?.includes("already set to be canceled")) {
585
869
  /**
586
- * in-case we missed the event from stripe, we set it manually
870
+ * in-case we missed the event from stripe, we sync the actual state
587
871
  * this is a rare case and should not happen
588
872
  */
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
- });
873
+ if (!isPendingCancel(subscription)) {
874
+ const stripeSub = await client.subscriptions.retrieve(activeSubscription.id);
875
+ await ctx.context.adapter.update({
876
+ model: "subscription",
877
+ update: {
878
+ cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
879
+ cancelAt: stripeSub.cancel_at ? /* @__PURE__ */ new Date(stripeSub.cancel_at * 1e3) : null,
880
+ canceledAt: stripeSub.canceled_at ? /* @__PURE__ */ new Date(stripeSub.canceled_at * 1e3) : null
881
+ },
882
+ where: [{
883
+ field: "id",
884
+ value: subscription.id
885
+ }]
886
+ });
887
+ }
597
888
  }
598
889
  throw ctx.error("BAD_REQUEST", {
599
890
  message: e.message,
600
891
  code: e.code
601
892
  });
602
893
  });
603
- return {
894
+ return ctx.json({
604
895
  url,
605
- redirect: true
606
- };
896
+ redirect: !ctx.body.disableRedirect
897
+ });
607
898
  });
608
899
  };
609
900
  const restoreSubscriptionBodySchema = z.object({
610
901
  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()
902
+ subscriptionId: z.string().meta({ description: "The Stripe subscription ID to restore. Eg: 'sub_1ABC2DEF3GHI4JKL'" }).optional(),
903
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional()
612
904
  });
613
905
  const restoreSubscription = (options) => {
614
906
  const client = options.stripeClient;
@@ -617,9 +909,10 @@ const restoreSubscription = (options) => {
617
909
  method: "POST",
618
910
  body: restoreSubscriptionBodySchema,
619
911
  metadata: { openapi: { operationId: "restoreSubscription" } },
620
- use: [sessionMiddleware, referenceMiddleware(subscriptionOptions, "restore-subscription")]
912
+ use: [stripeSessionMiddleware, referenceMiddleware(subscriptionOptions, "restore-subscription")]
621
913
  }, async (ctx) => {
622
- const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
914
+ const customerType = ctx.body.customerType || "user";
915
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
623
916
  let subscription = ctx.body.subscriptionId ? await ctx.context.adapter.findOne({
624
917
  model: "subscription",
625
918
  where: [{
@@ -632,34 +925,42 @@ const restoreSubscription = (options) => {
632
925
  field: "referenceId",
633
926
  value: referenceId
634
927
  }]
635
- }).then((subs) => subs.find((sub) => sub.status === "active" || sub.status === "trialing"));
928
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub)));
636
929
  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
- }]
930
+ if (!subscription || !subscription.stripeCustomerId) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
931
+ if (!isActiveOrTrialing(subscription)) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE);
932
+ if (!isPendingCancel(subscription)) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION);
933
+ const activeSubscription = await client.subscriptions.list({ customer: subscription.stripeCustomerId }).then((res) => res.data.filter((sub) => isActiveOrTrialing(sub))[0]);
934
+ if (!activeSubscription) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
935
+ const updateParams = {};
936
+ if (activeSubscription.cancel_at) updateParams.cancel_at = "";
937
+ else if (activeSubscription.cancel_at_period_end) updateParams.cancel_at_period_end = false;
938
+ const newSub = await client.subscriptions.update(activeSubscription.id, updateParams).catch((e) => {
939
+ throw ctx.error("BAD_REQUEST", {
940
+ message: e.message,
941
+ code: e.code
654
942
  });
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
- }
943
+ });
944
+ await ctx.context.adapter.update({
945
+ model: "subscription",
946
+ update: {
947
+ cancelAtPeriodEnd: false,
948
+ cancelAt: null,
949
+ canceledAt: null,
950
+ updatedAt: /* @__PURE__ */ new Date()
951
+ },
952
+ where: [{
953
+ field: "id",
954
+ value: subscription.id
955
+ }]
956
+ });
957
+ return ctx.json(newSub);
660
958
  });
661
959
  };
662
- const listActiveSubscriptionsQuerySchema = z.optional(z.object({ referenceId: z.string().meta({ description: "Reference id of the subscription to list. Eg: '123'" }).optional() }));
960
+ const listActiveSubscriptionsQuerySchema = z.optional(z.object({
961
+ referenceId: z.string().meta({ description: "Reference id of the subscription to list. Eg: '123'" }).optional(),
962
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional()
963
+ }));
663
964
  /**
664
965
  * ### Endpoint
665
966
  *
@@ -681,13 +982,15 @@ const listActiveSubscriptions = (options) => {
681
982
  method: "GET",
682
983
  query: listActiveSubscriptionsQuerySchema,
683
984
  metadata: { openapi: { operationId: "listActiveSubscriptions" } },
684
- use: [sessionMiddleware, referenceMiddleware(subscriptionOptions, "list-subscription")]
985
+ use: [stripeSessionMiddleware, referenceMiddleware(subscriptionOptions, "list-subscription")]
685
986
  }, async (ctx) => {
987
+ const customerType = ctx.query?.customerType || "user";
988
+ const referenceId = ctx.query?.referenceId || getReferenceId(ctx.context.session, customerType, options);
686
989
  const subscriptions$1 = await ctx.context.adapter.findMany({
687
990
  model: "subscription",
688
991
  where: [{
689
992
  field: "referenceId",
690
- value: ctx.query?.referenceId || ctx.context.session.user.id
993
+ value: referenceId
691
994
  }]
692
995
  });
693
996
  if (!subscriptions$1.length) return [];
@@ -700,9 +1003,7 @@ const listActiveSubscriptions = (options) => {
700
1003
  limits: plan?.limits,
701
1004
  priceId: plan?.priceId
702
1005
  };
703
- }).filter((sub) => {
704
- return sub.status === "active" || sub.status === "trialing";
705
- });
1006
+ }).filter((sub) => isActiveOrTrialing(sub));
706
1007
  return ctx.json(subs);
707
1008
  });
708
1009
  };
@@ -716,10 +1017,9 @@ const subscriptionSuccess = (options) => {
716
1017
  use: [originCheck((ctx) => ctx.query.callbackURL)]
717
1018
  }, async (ctx) => {
718
1019
  if (!ctx.query || !ctx.query.callbackURL || !ctx.query.subscriptionId) throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
1020
+ const { callbackURL, subscriptionId } = ctx.query;
719
1021
  const session = await getSessionFromCtx(ctx);
720
1022
  if (!session) throw ctx.redirect(getUrl(ctx, ctx.query?.callbackURL || "/"));
721
- const { user: user$1 } = session;
722
- const { callbackURL, subscriptionId } = ctx.query;
723
1023
  const subscription = await ctx.context.adapter.findOne({
724
1024
  model: "subscription",
725
1025
  where: [{
@@ -727,38 +1027,53 @@ const subscriptionSuccess = (options) => {
727
1027
  value: subscriptionId
728
1028
  }]
729
1029
  });
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) {
1030
+ if (!subscription) {
1031
+ ctx.context.logger.warn(`Subscription record not found for subscriptionId: ${subscriptionId}`);
1032
+ throw ctx.redirect(getUrl(ctx, callbackURL));
1033
+ }
1034
+ if (isActiveOrTrialing(subscription)) throw ctx.redirect(getUrl(ctx, callbackURL));
1035
+ const customerId = subscription.stripeCustomerId || session.user.stripeCustomerId;
1036
+ if (!customerId) throw ctx.redirect(getUrl(ctx, callbackURL));
1037
+ const stripeSubscription = await client.subscriptions.list({
1038
+ customer: customerId,
1039
+ status: "active"
1040
+ }).then((res) => res.data[0]).catch((error) => {
760
1041
  ctx.context.logger.error("Error fetching subscription from Stripe", error);
1042
+ throw ctx.redirect(getUrl(ctx, callbackURL));
1043
+ });
1044
+ if (!stripeSubscription) throw ctx.redirect(getUrl(ctx, callbackURL));
1045
+ const subscriptionItem = stripeSubscription.items.data[0];
1046
+ if (!subscriptionItem) {
1047
+ ctx.context.logger.warn(`No subscription items found for Stripe subscription ${stripeSubscription.id}`);
1048
+ throw ctx.redirect(getUrl(ctx, callbackURL));
1049
+ }
1050
+ const plan = await getPlanByPriceInfo(options, subscriptionItem.price.id, subscriptionItem.price.lookup_key);
1051
+ if (!plan) {
1052
+ ctx.context.logger.warn(`Plan not found for price ${subscriptionItem.price.id}`);
1053
+ throw ctx.redirect(getUrl(ctx, callbackURL));
761
1054
  }
1055
+ await ctx.context.adapter.update({
1056
+ model: "subscription",
1057
+ update: {
1058
+ status: stripeSubscription.status,
1059
+ seats: subscriptionItem.quantity || 1,
1060
+ plan: plan.name.toLowerCase(),
1061
+ periodEnd: /* @__PURE__ */ new Date(subscriptionItem.current_period_end * 1e3),
1062
+ periodStart: /* @__PURE__ */ new Date(subscriptionItem.current_period_start * 1e3),
1063
+ stripeSubscriptionId: stripeSubscription.id,
1064
+ cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end,
1065
+ cancelAt: stripeSubscription.cancel_at ? /* @__PURE__ */ new Date(stripeSubscription.cancel_at * 1e3) : null,
1066
+ canceledAt: stripeSubscription.canceled_at ? /* @__PURE__ */ new Date(stripeSubscription.canceled_at * 1e3) : null,
1067
+ ...stripeSubscription.trial_start && stripeSubscription.trial_end ? {
1068
+ trialStart: /* @__PURE__ */ new Date(stripeSubscription.trial_start * 1e3),
1069
+ trialEnd: /* @__PURE__ */ new Date(stripeSubscription.trial_end * 1e3)
1070
+ } : {}
1071
+ },
1072
+ where: [{
1073
+ field: "id",
1074
+ value: subscription.id
1075
+ }]
1076
+ });
762
1077
  throw ctx.redirect(getUrl(ctx, callbackURL));
763
1078
  });
764
1079
  };
@@ -767,7 +1082,9 @@ const createBillingPortalBodySchema = z.object({
767
1082
  return typeof localization === "string";
768
1083
  }).optional(),
769
1084
  referenceId: z.string().optional(),
770
- returnUrl: z.string().default("/")
1085
+ customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
1086
+ returnUrl: z.string().default("/"),
1087
+ disableRedirect: z.boolean().meta({ description: "Disable redirect after creating billing portal session. Eg: true" }).default(false)
771
1088
  });
772
1089
  const createBillingPortal = (options) => {
773
1090
  const client = options.stripeClient;
@@ -777,22 +1094,41 @@ const createBillingPortal = (options) => {
777
1094
  body: createBillingPortalBodySchema,
778
1095
  metadata: { openapi: { operationId: "createBillingPortal" } },
779
1096
  use: [
780
- sessionMiddleware,
781
- originCheck((ctx) => ctx.body.returnUrl),
782
- referenceMiddleware(subscriptionOptions, "billing-portal")
1097
+ stripeSessionMiddleware,
1098
+ referenceMiddleware(subscriptionOptions, "billing-portal"),
1099
+ originCheck((ctx) => ctx.body.returnUrl)
783
1100
  ]
784
1101
  }, async (ctx) => {
785
1102
  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" });
1103
+ const customerType = ctx.body.customerType || "user";
1104
+ const referenceId = ctx.body.referenceId || getReferenceId(ctx.context.session, customerType, options);
1105
+ let customerId;
1106
+ if (customerType === "organization") {
1107
+ customerId = (await ctx.context.adapter.findOne({
1108
+ model: "organization",
1109
+ where: [{
1110
+ field: "id",
1111
+ value: referenceId
1112
+ }]
1113
+ }))?.stripeCustomerId;
1114
+ if (!customerId) customerId = (await ctx.context.adapter.findMany({
1115
+ model: "subscription",
1116
+ where: [{
1117
+ field: "referenceId",
1118
+ value: referenceId
1119
+ }]
1120
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub))))?.stripeCustomerId;
1121
+ } else {
1122
+ customerId = user$1.stripeCustomerId;
1123
+ if (!customerId) customerId = (await ctx.context.adapter.findMany({
1124
+ model: "subscription",
1125
+ where: [{
1126
+ field: "referenceId",
1127
+ value: referenceId
1128
+ }]
1129
+ }).then((subs) => subs.find((sub) => isActiveOrTrialing(sub))))?.stripeCustomerId;
1130
+ }
1131
+ if (!customerId) throw APIError$1.from("NOT_FOUND", STRIPE_ERROR_CODES.CUSTOMER_NOT_FOUND);
796
1132
  try {
797
1133
  const { url } = await client.billingPortal.sessions.create({
798
1134
  locale: ctx.body.locale,
@@ -801,11 +1137,11 @@ const createBillingPortal = (options) => {
801
1137
  });
802
1138
  return ctx.json({
803
1139
  url,
804
- redirect: true
1140
+ redirect: !ctx.body.disableRedirect
805
1141
  });
806
1142
  } catch (error) {
807
1143
  ctx.context.logger.error("Error creating billing portal session", error);
808
- throw new APIError("BAD_REQUEST", { message: error.message });
1144
+ throw APIError$1.from("INTERNAL_SERVER_ERROR", STRIPE_ERROR_CODES.UNABLE_TO_CREATE_BILLING_PORTAL);
809
1145
  }
810
1146
  });
811
1147
  };
@@ -820,26 +1156,31 @@ const stripeWebhook = (options) => {
820
1156
  cloneRequest: true,
821
1157
  disableBody: true
822
1158
  }, async (ctx) => {
823
- if (!ctx.request?.body) throw new APIError("INTERNAL_SERVER_ERROR");
824
- const buf = await ctx.request.text();
1159
+ if (!ctx.request?.body) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.INVALID_REQUEST_BODY);
825
1160
  const sig = ctx.request.headers.get("stripe-signature");
1161
+ if (!sig) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.STRIPE_SIGNATURE_NOT_FOUND);
826
1162
  const webhookSecret = options.stripeWebhookSecret;
1163
+ if (!webhookSecret) throw APIError$1.from("INTERNAL_SERVER_ERROR", STRIPE_ERROR_CODES.STRIPE_WEBHOOK_SECRET_NOT_FOUND);
1164
+ const payload = await ctx.request.text();
827
1165
  let event;
828
1166
  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);
1167
+ if (typeof client.webhooks.constructEventAsync === "function") event = await client.webhooks.constructEventAsync(payload, sig, webhookSecret);
1168
+ else event = client.webhooks.constructEvent(payload, sig, webhookSecret);
832
1169
  } catch (err) {
833
1170
  ctx.context.logger.error(`${err.message}`);
834
- throw new APIError("BAD_REQUEST", { message: `Webhook Error: ${err.message}` });
1171
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.FAILED_TO_CONSTRUCT_STRIPE_EVENT);
835
1172
  }
836
- if (!event) throw new APIError("BAD_REQUEST", { message: "Failed to construct event" });
1173
+ if (!event) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.FAILED_TO_CONSTRUCT_STRIPE_EVENT);
837
1174
  try {
838
1175
  switch (event.type) {
839
1176
  case "checkout.session.completed":
840
1177
  await onCheckoutSessionCompleted(ctx, options, event);
841
1178
  await options.onEvent?.(event);
842
1179
  break;
1180
+ case "customer.subscription.created":
1181
+ await onSubscriptionCreated(ctx, options, event);
1182
+ await options.onEvent?.(event);
1183
+ break;
843
1184
  case "customer.subscription.updated":
844
1185
  await onSubscriptionUpdated(ctx, options, event);
845
1186
  await options.onEvent?.(event);
@@ -854,23 +1195,11 @@ const stripeWebhook = (options) => {
854
1195
  }
855
1196
  } catch (e) {
856
1197
  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." });
1198
+ throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.STRIPE_WEBHOOK_ERROR);
858
1199
  }
859
1200
  return ctx.json({ success: true });
860
1201
  });
861
1202
  };
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
1203
 
875
1204
  //#endregion
876
1205
  //#region src/schema.ts
@@ -916,6 +1245,18 @@ const subscriptions = { subscription: { fields: {
916
1245
  required: false,
917
1246
  defaultValue: false
918
1247
  },
1248
+ cancelAt: {
1249
+ type: "date",
1250
+ required: false
1251
+ },
1252
+ canceledAt: {
1253
+ type: "date",
1254
+ required: false
1255
+ },
1256
+ endedAt: {
1257
+ type: "date",
1258
+ required: false
1259
+ },
919
1260
  seats: {
920
1261
  type: "number",
921
1262
  required: false
@@ -925,6 +1266,10 @@ const user = { user: { fields: { stripeCustomerId: {
925
1266
  type: "string",
926
1267
  required: false
927
1268
  } } } };
1269
+ const organization = { organization: { fields: { stripeCustomerId: {
1270
+ type: "string",
1271
+ required: false
1272
+ } } } };
928
1273
  const getSchema = (options) => {
929
1274
  let baseSchema = {};
930
1275
  if (options.subscription?.enabled) baseSchema = {
@@ -932,6 +1277,10 @@ const getSchema = (options) => {
932
1277
  ...user
933
1278
  };
934
1279
  else baseSchema = { ...user };
1280
+ if (options.organization?.enabled) baseSchema = {
1281
+ ...baseSchema,
1282
+ ...organization
1283
+ };
935
1284
  if (options.schema && !options.subscription?.enabled && "subscription" in options.schema) {
936
1285
  const { subscription: _subscription, ...restSchema } = options.schema;
937
1286
  return mergeSchema(baseSchema, restSchema);
@@ -941,16 +1290,6 @@ const getSchema = (options) => {
941
1290
 
942
1291
  //#endregion
943
1292
  //#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
1293
  const stripe = (options) => {
955
1294
  const client = options.stripeClient;
956
1295
  const subscriptionEndpoints = {
@@ -969,13 +1308,70 @@ const stripe = (options) => {
969
1308
  ...options.subscription?.enabled ? subscriptionEndpoints : {}
970
1309
  },
971
1310
  init(ctx) {
1311
+ if (options.organization?.enabled) {
1312
+ const orgPlugin = ctx.getPlugin("organization");
1313
+ if (!orgPlugin) {
1314
+ ctx.logger.error(`Organization plugin not found`);
1315
+ return;
1316
+ }
1317
+ const existingHooks = orgPlugin.options.organizationHooks ?? {};
1318
+ /**
1319
+ * Sync organization name to Stripe customer
1320
+ */
1321
+ const afterUpdateStripeOrg = async (data) => {
1322
+ const { organization: organization$1 } = data;
1323
+ if (!organization$1?.stripeCustomerId) return;
1324
+ try {
1325
+ const stripeCustomer = await client.customers.retrieve(organization$1.stripeCustomerId);
1326
+ if (stripeCustomer.deleted) {
1327
+ ctx.logger.warn(`Stripe customer ${organization$1.stripeCustomerId} was deleted`);
1328
+ return;
1329
+ }
1330
+ if (organization$1.name !== stripeCustomer.name) {
1331
+ await client.customers.update(organization$1.stripeCustomerId, { name: organization$1.name });
1332
+ ctx.logger.info(`Synced organization name to Stripe: "${stripeCustomer.name}" → "${organization$1.name}"`);
1333
+ }
1334
+ } catch (e) {
1335
+ ctx.logger.error(`Failed to sync organization to Stripe: ${e.message}`);
1336
+ }
1337
+ };
1338
+ /**
1339
+ * Block deletion if organization has active subscriptions
1340
+ */
1341
+ const beforeDeleteStripeOrg = async (data) => {
1342
+ const { organization: organization$1 } = data;
1343
+ if (!organization$1.stripeCustomerId) return;
1344
+ try {
1345
+ const subscriptions$1 = await client.subscriptions.list({
1346
+ customer: organization$1.stripeCustomerId,
1347
+ status: "all",
1348
+ limit: 100
1349
+ });
1350
+ 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);
1351
+ } catch (error) {
1352
+ if (error instanceof APIError) throw error;
1353
+ ctx.logger.error(`Failed to check organization subscriptions: ${error.message}`);
1354
+ throw error;
1355
+ }
1356
+ };
1357
+ orgPlugin.options.organizationHooks = {
1358
+ ...existingHooks,
1359
+ afterUpdateOrganization: existingHooks.afterUpdateOrganization ? async (data) => {
1360
+ await existingHooks.afterUpdateOrganization(data);
1361
+ await afterUpdateStripeOrg(data);
1362
+ } : afterUpdateStripeOrg,
1363
+ beforeDeleteOrganization: existingHooks.beforeDeleteOrganization ? async (data) => {
1364
+ await existingHooks.beforeDeleteOrganization(data);
1365
+ await beforeDeleteStripeOrg(data);
1366
+ } : beforeDeleteStripeOrg
1367
+ };
1368
+ }
972
1369
  return { options: { databaseHooks: { user: {
973
1370
  create: { async after(user$1, ctx$1) {
974
- if (!ctx$1 || !options.createCustomerOnSignUp) return;
1371
+ if (!ctx$1 || !options.createCustomerOnSignUp || user$1.stripeCustomerId) return;
975
1372
  try {
976
- if (user$1.stripeCustomerId) return;
977
- let stripeCustomer = (await client.customers.list({
978
- email: user$1.email,
1373
+ let stripeCustomer = (await client.customers.search({
1374
+ query: `email:"${escapeStripeSearchValue(user$1.email)}" AND -metadata["customerType"]:"organization"`,
979
1375
  limit: 1
980
1376
  })).data[0];
981
1377
  if (stripeCustomer) {
@@ -995,7 +1391,10 @@ const stripe = (options) => {
995
1391
  const params = defu({
996
1392
  email: user$1.email,
997
1393
  name: user$1.name,
998
- metadata: { userId: user$1.id }
1394
+ metadata: {
1395
+ userId: user$1.id,
1396
+ customerType: "user"
1397
+ }
999
1398
  }, extraCreateParams);
1000
1399
  stripeCustomer = await client.customers.create(params);
1001
1400
  await ctx$1.context.internalAdapter.updateUser(user$1.id, { stripeCustomerId: stripeCustomer.id });
@@ -1012,17 +1411,15 @@ const stripe = (options) => {
1012
1411
  }
1013
1412
  } },
1014
1413
  update: { async after(user$1, ctx$1) {
1015
- if (!ctx$1) return;
1414
+ if (!ctx$1 || !user$1.stripeCustomerId) return;
1016
1415
  try {
1017
- const userWithStripe = user$1;
1018
- if (!userWithStripe.stripeCustomerId) return;
1019
- const stripeCustomer = await client.customers.retrieve(userWithStripe.stripeCustomerId);
1416
+ const stripeCustomer = await client.customers.retrieve(user$1.stripeCustomerId);
1020
1417
  if (stripeCustomer.deleted) {
1021
- ctx$1.context.logger.warn(`Stripe customer ${userWithStripe.stripeCustomerId} was deleted, cannot update email`);
1418
+ ctx$1.context.logger.warn(`Stripe customer ${user$1.stripeCustomerId} was deleted, cannot update email`);
1022
1419
  return;
1023
1420
  }
1024
1421
  if (stripeCustomer.email !== user$1.email) {
1025
- await client.customers.update(userWithStripe.stripeCustomerId, { email: user$1.email });
1422
+ await client.customers.update(user$1.stripeCustomerId, { email: user$1.email });
1026
1423
  ctx$1.context.logger.info(`Updated Stripe customer email from ${stripeCustomer.email} to ${user$1.email}`);
1027
1424
  }
1028
1425
  } catch (e) {