@better-auth/stripe 1.5.0-beta.2 → 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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@better-auth/stripe",
3
3
  "author": "Bereket Engida",
4
- "version": "1.5.0-beta.2",
4
+ "version": "1.5.0-beta.3",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
7
7
  "types": "dist/index.d.mts",
@@ -50,15 +50,15 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "stripe": "^18 || ^19 || ^20",
53
- "@better-auth/core": "1.5.0-beta.2",
54
- "better-auth": "1.5.0-beta.2"
53
+ "@better-auth/core": "1.5.0-beta.3",
54
+ "better-auth": "1.5.0-beta.3"
55
55
  },
56
56
  "devDependencies": {
57
- "better-call": "1.1.7",
57
+ "better-call": "1.1.8",
58
58
  "stripe": "^20.0.0",
59
59
  "tsdown": "^0.17.2",
60
- "@better-auth/core": "1.5.0-beta.2",
61
- "better-auth": "1.5.0-beta.2"
60
+ "@better-auth/core": "1.5.0-beta.3",
61
+ "better-auth": "1.5.0-beta.3"
62
62
  },
63
63
  "scripts": {
64
64
  "test": "vitest",
@@ -1,14 +1,30 @@
1
1
  import { defineErrorCodes } from "@better-auth/core/utils";
2
2
 
3
3
  export const STRIPE_ERROR_CODES = defineErrorCodes({
4
+ UNAUTHORIZED: "Unauthorized access",
5
+ INVALID_REQUEST_BODY: "Invalid request body",
4
6
  SUBSCRIPTION_NOT_FOUND: "Subscription not found",
5
7
  SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
6
8
  ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
9
+ REFERENCE_ID_NOT_ALLOWED: "Reference id is not allowed",
10
+ CUSTOMER_NOT_FOUND: "Stripe customer not found for this user",
7
11
  UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
12
+ UNABLE_TO_CREATE_BILLING_PORTAL: "Unable to create billing portal session",
13
+ STRIPE_SIGNATURE_NOT_FOUND: "Stripe signature not found",
14
+ STRIPE_WEBHOOK_SECRET_NOT_FOUND: "Stripe webhook secret not found",
15
+ STRIPE_WEBHOOK_ERROR: "Stripe webhook error",
16
+ FAILED_TO_CONSTRUCT_STRIPE_EVENT: "Failed to construct Stripe event",
8
17
  FAILED_TO_FETCH_PLANS: "Failed to fetch plans",
9
18
  EMAIL_VERIFICATION_REQUIRED:
10
19
  "Email verification is required before you can subscribe to a plan",
11
20
  SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
12
21
  SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION:
13
22
  "Subscription is not scheduled for cancellation",
23
+ ORGANIZATION_NOT_FOUND: "Organization not found",
24
+ ORGANIZATION_SUBSCRIPTION_NOT_ENABLED:
25
+ "Organization subscription is not enabled",
26
+ ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION:
27
+ "Cannot delete organization with active subscription",
28
+ ORGANIZATION_REFERENCE_ID_REQUIRED:
29
+ "Reference ID is required. Provide referenceId or set activeOrganizationId in session",
14
30
  });
package/src/hooks.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { GenericEndpointContext } from "@better-auth/core";
2
2
  import type { User } from "@better-auth/core/db";
3
+ import type { Organization } from "better-auth/plugins/organization";
3
4
  import type Stripe from "stripe";
4
- import type { InputSubscription, StripeOptions, Subscription } from "./types";
5
+ import type { CustomerType, StripeOptions, Subscription } from "./types";
5
6
  import {
6
7
  getPlanByPriceInfo,
7
8
  isActiveOrTrialing,
@@ -9,6 +10,32 @@ import {
9
10
  isStripePendingCancel,
10
11
  } from "./utils";
11
12
 
13
+ /**
14
+ * Find organization or user by stripeCustomerId.
15
+ * @internal
16
+ */
17
+ async function findReferenceByStripeCustomerId(
18
+ ctx: GenericEndpointContext,
19
+ options: StripeOptions,
20
+ stripeCustomerId: string,
21
+ ): Promise<{ customerType: CustomerType; referenceId: string } | null> {
22
+ if (options.organization?.enabled) {
23
+ const org = await ctx.context.adapter.findOne<Organization>({
24
+ model: "organization",
25
+ where: [{ field: "stripeCustomerId", value: stripeCustomerId }],
26
+ });
27
+ if (org) return { customerType: "organization", referenceId: org.id };
28
+ }
29
+
30
+ const user = await ctx.context.adapter.findOne<User>({
31
+ model: "user",
32
+ where: [{ field: "stripeCustomerId", value: stripeCustomerId }],
33
+ });
34
+ if (user) return { customerType: "user", referenceId: user.id };
35
+
36
+ return null;
37
+ }
38
+
12
39
  export async function onCheckoutSessionCompleted(
13
40
  ctx: GenericEndpointContext,
14
41
  options: StripeOptions,
@@ -23,8 +50,16 @@ export async function onCheckoutSessionCompleted(
23
50
  const subscription = await client.subscriptions.retrieve(
24
51
  checkoutSession.subscription as string,
25
52
  );
26
- const priceId = subscription.items.data[0]?.price.id;
27
- const priceLookupKey = subscription.items.data[0]?.price.lookup_key || null;
53
+ const subscriptionItem = subscription.items.data[0];
54
+ if (!subscriptionItem) {
55
+ ctx.context.logger.warn(
56
+ `Stripe webhook warning: Subscription ${subscription.id} has no items`,
57
+ );
58
+ return;
59
+ }
60
+
61
+ const priceId = subscriptionItem.price.id;
62
+ const priceLookupKey = subscriptionItem.price.lookup_key;
28
63
  const plan = await getPlanByPriceInfo(
29
64
  options,
30
65
  priceId as string,
@@ -35,7 +70,7 @@ export async function onCheckoutSessionCompleted(
35
70
  checkoutSession?.client_reference_id ||
36
71
  checkoutSession?.metadata?.referenceId;
37
72
  const subscriptionId = checkoutSession?.metadata?.subscriptionId;
38
- const seats = subscription.items.data[0]!.quantity;
73
+ const seats = subscriptionItem.quantity;
39
74
  if (referenceId && subscriptionId) {
40
75
  const trial =
41
76
  subscription.trial_start && subscription.trial_end
@@ -45,40 +80,35 @@ export async function onCheckoutSessionCompleted(
45
80
  }
46
81
  : {};
47
82
 
48
- let dbSubscription =
49
- await ctx.context.adapter.update<InputSubscription>({
50
- model: "subscription",
51
- update: {
52
- plan: plan.name.toLowerCase(),
53
- status: subscription.status,
54
- updatedAt: new Date(),
55
- periodStart: new Date(
56
- subscription.items.data[0]!.current_period_start * 1000,
57
- ),
58
- periodEnd: new Date(
59
- subscription.items.data[0]!.current_period_end * 1000,
60
- ),
61
- stripeSubscriptionId: checkoutSession.subscription as string,
62
- cancelAtPeriodEnd: subscription.cancel_at_period_end,
63
- cancelAt: subscription.cancel_at
64
- ? new Date(subscription.cancel_at * 1000)
65
- : null,
66
- canceledAt: subscription.canceled_at
67
- ? new Date(subscription.canceled_at * 1000)
68
- : null,
69
- endedAt: subscription.ended_at
70
- ? new Date(subscription.ended_at * 1000)
71
- : null,
72
- seats: seats,
73
- ...trial,
83
+ let dbSubscription = await ctx.context.adapter.update<Subscription>({
84
+ model: "subscription",
85
+ update: {
86
+ plan: plan.name.toLowerCase(),
87
+ status: subscription.status,
88
+ updatedAt: new Date(),
89
+ periodStart: new Date(subscriptionItem.current_period_start * 1000),
90
+ periodEnd: new Date(subscriptionItem.current_period_end * 1000),
91
+ stripeSubscriptionId: checkoutSession.subscription as string,
92
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
93
+ cancelAt: subscription.cancel_at
94
+ ? new Date(subscription.cancel_at * 1000)
95
+ : null,
96
+ canceledAt: subscription.canceled_at
97
+ ? new Date(subscription.canceled_at * 1000)
98
+ : null,
99
+ endedAt: subscription.ended_at
100
+ ? new Date(subscription.ended_at * 1000)
101
+ : null,
102
+ seats: seats,
103
+ ...trial,
104
+ },
105
+ where: [
106
+ {
107
+ field: "id",
108
+ value: subscriptionId,
74
109
  },
75
- where: [
76
- {
77
- field: "id",
78
- value: subscriptionId,
79
- },
80
- ],
81
- });
110
+ ],
111
+ });
82
112
 
83
113
  if (trial.trialStart && plan.freeTrial?.onTrialStart) {
84
114
  await plan.freeTrial.onTrialStart(dbSubscription as Subscription);
@@ -132,39 +162,34 @@ export async function onSubscriptionCreated(
132
162
  }
133
163
 
134
164
  // Check if subscription already exists in database
165
+ const subscriptionId = subscriptionCreated.metadata?.subscriptionId;
135
166
  const existingSubscription =
136
167
  await ctx.context.adapter.findOne<Subscription>({
137
168
  model: "subscription",
138
- where: [
139
- {
140
- field: "stripeSubscriptionId",
141
- value: subscriptionCreated.id,
142
- },
143
- ],
169
+ where: subscriptionId
170
+ ? [{ field: "id", value: subscriptionId }]
171
+ : [{ field: "stripeSubscriptionId", value: subscriptionCreated.id }], // Probably won't match since it's not set yet
144
172
  });
145
173
  if (existingSubscription) {
146
174
  ctx.context.logger.info(
147
- `Stripe webhook: Subscription ${subscriptionCreated.id} already exists in database, skipping creation`,
175
+ `Stripe webhook: Subscription already exists in database (id: ${existingSubscription.id}), skipping creation`,
148
176
  );
149
177
  return;
150
178
  }
151
179
 
152
- // Find user by stripeCustomerId
153
- const user = await ctx.context.adapter.findOne<User>({
154
- model: "user",
155
- where: [
156
- {
157
- field: "stripeCustomerId",
158
- value: stripeCustomerId,
159
- },
160
- ],
161
- });
162
- if (!user) {
180
+ // Find reference
181
+ const reference = await findReferenceByStripeCustomerId(
182
+ ctx,
183
+ options,
184
+ stripeCustomerId,
185
+ );
186
+ if (!reference) {
163
187
  ctx.context.logger.warn(
164
- `Stripe webhook warning: No user found with stripeCustomerId: ${stripeCustomerId}`,
188
+ `Stripe webhook warning: No user or organization found with stripeCustomerId: ${stripeCustomerId}`,
165
189
  );
166
190
  return;
167
191
  }
192
+ const { referenceId, customerType } = reference;
168
193
 
169
194
  const subscriptionItem = subscriptionCreated.items.data[0];
170
195
  if (!subscriptionItem) {
@@ -200,8 +225,8 @@ export async function onSubscriptionCreated(
200
225
  const newSubscription = await ctx.context.adapter.create<Subscription>({
201
226
  model: "subscription",
202
227
  data: {
203
- referenceId: user.id,
204
- stripeCustomerId: stripeCustomerId,
228
+ referenceId,
229
+ stripeCustomerId,
205
230
  stripeSubscriptionId: subscriptionCreated.id,
206
231
  status: subscriptionCreated.status,
207
232
  plan: plan.name.toLowerCase(),
@@ -214,10 +239,10 @@ export async function onSubscriptionCreated(
214
239
  });
215
240
 
216
241
  ctx.context.logger.info(
217
- `Stripe webhook: Created subscription ${subscriptionCreated.id} for user ${user.id} from dashboard`,
242
+ `Stripe webhook: Created subscription ${subscriptionCreated.id} for ${customerType} ${referenceId} from dashboard`,
218
243
  );
219
244
 
220
- await options.subscription?.onSubscriptionCreated?.({
245
+ await options.subscription.onSubscriptionCreated?.({
221
246
  event,
222
247
  subscription: newSubscription,
223
248
  stripeSubscription: subscriptionCreated,
@@ -238,9 +263,16 @@ export async function onSubscriptionUpdated(
238
263
  return;
239
264
  }
240
265
  const subscriptionUpdated = event.data.object as Stripe.Subscription;
241
- const priceId = subscriptionUpdated.items.data[0]!.price.id;
242
- const priceLookupKey =
243
- subscriptionUpdated.items.data[0]!.price.lookup_key || null;
266
+ const subscriptionItem = subscriptionUpdated.items.data[0];
267
+ if (!subscriptionItem) {
268
+ ctx.context.logger.warn(
269
+ `Stripe webhook warning: Subscription ${subscriptionUpdated.id} has no items`,
270
+ );
271
+ return;
272
+ }
273
+
274
+ const priceId = subscriptionItem.price.id;
275
+ const priceLookupKey = subscriptionItem.price.lookup_key;
244
276
  const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
245
277
 
246
278
  const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
@@ -272,7 +304,6 @@ export async function onSubscriptionUpdated(
272
304
  }
273
305
  }
274
306
 
275
- const seats = subscriptionUpdated.items.data[0]!.quantity;
276
307
  const updatedSubscription = await ctx.context.adapter.update<Subscription>({
277
308
  model: "subscription",
278
309
  update: {
@@ -284,12 +315,8 @@ export async function onSubscriptionUpdated(
284
315
  : {}),
285
316
  updatedAt: new Date(),
286
317
  status: subscriptionUpdated.status,
287
- periodStart: new Date(
288
- subscriptionUpdated.items.data[0]!.current_period_start * 1000,
289
- ),
290
- periodEnd: new Date(
291
- subscriptionUpdated.items.data[0]!.current_period_end * 1000,
292
- ),
318
+ periodStart: new Date(subscriptionItem.current_period_start * 1000),
319
+ periodEnd: new Date(subscriptionItem.current_period_end * 1000),
293
320
  cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
294
321
  cancelAt: subscriptionUpdated.cancel_at
295
322
  ? new Date(subscriptionUpdated.cancel_at * 1000)
@@ -300,7 +327,7 @@ export async function onSubscriptionUpdated(
300
327
  endedAt: subscriptionUpdated.ended_at
301
328
  ? new Date(subscriptionUpdated.ended_at * 1000)
302
329
  : null,
303
- seats: seats,
330
+ seats: subscriptionItem.quantity,
304
331
  stripeSubscriptionId: subscriptionUpdated.id,
305
332
  },
306
333
  where: [
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { defineErrorCodes } from "@better-auth/core/utils";
2
- import type { BetterAuthPlugin } from "better-auth";
1
+ import type { BetterAuthPlugin, User } from "better-auth";
2
+ import { APIError } from "better-auth";
3
+ import type { Organization } from "better-auth/plugins/organization";
3
4
  import { defu } from "defu";
4
5
  import type Stripe from "stripe";
6
+ import { STRIPE_ERROR_CODES } from "./error-codes";
5
7
  import {
6
8
  cancelSubscription,
7
9
  cancelSubscriptionCallback,
@@ -18,20 +20,18 @@ import type {
18
20
  StripePlan,
19
21
  Subscription,
20
22
  SubscriptionOptions,
23
+ WithStripeCustomerId,
21
24
  } from "./types";
25
+ import { escapeStripeSearchValue } from "./utils";
22
26
 
23
- const STRIPE_ERROR_CODES = defineErrorCodes({
24
- SUBSCRIPTION_NOT_FOUND: "Subscription not found",
25
- SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
26
- ALREADY_SUBSCRIBED_PLAN: "You're already subscribed to this plan",
27
- UNABLE_TO_CREATE_CUSTOMER: "Unable to create customer",
28
- FAILED_TO_FETCH_PLANS: "Failed to fetch plans",
29
- EMAIL_VERIFICATION_REQUIRED:
30
- "Email verification is required before you can subscribe to a plan",
31
- SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
32
- SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION:
33
- "Subscription is not scheduled for cancellation",
34
- });
27
+ declare module "@better-auth/core" {
28
+ // biome-ignore lint/correctness/noUnusedVariables: Auth and Context need to be same as declared in the module
29
+ interface BetterAuthPluginRegistry<Auth, Context> {
30
+ stripe: {
31
+ creator: typeof stripe;
32
+ };
33
+ }
34
+ }
35
35
 
36
36
  export const stripe = <O extends StripeOptions>(options: O) => {
37
37
  const client = options.stripeClient;
@@ -59,31 +59,134 @@ export const stripe = <O extends StripeOptions>(options: O) => {
59
59
  : {}),
60
60
  },
61
61
  init(ctx) {
62
+ if (options.organization?.enabled) {
63
+ const orgPlugin = ctx.getPlugin("organization");
64
+ if (!orgPlugin) {
65
+ ctx.logger.error(`Organization plugin not found`);
66
+ return;
67
+ }
68
+
69
+ const existingHooks = orgPlugin.options.organizationHooks ?? {};
70
+
71
+ /**
72
+ * Sync organization name to Stripe customer
73
+ */
74
+ const afterUpdateStripeOrg = async (data: {
75
+ organization: (Organization & WithStripeCustomerId) | null;
76
+ user: User;
77
+ }) => {
78
+ const { organization } = data;
79
+ if (!organization?.stripeCustomerId) return;
80
+
81
+ try {
82
+ const stripeCustomer = await client.customers.retrieve(
83
+ organization.stripeCustomerId,
84
+ );
85
+
86
+ if (stripeCustomer.deleted) {
87
+ ctx.logger.warn(
88
+ `Stripe customer ${organization.stripeCustomerId} was deleted`,
89
+ );
90
+ return;
91
+ }
92
+
93
+ // Update Stripe customer if name changed
94
+ if (organization.name !== stripeCustomer.name) {
95
+ await client.customers.update(organization.stripeCustomerId, {
96
+ name: organization.name,
97
+ });
98
+ ctx.logger.info(
99
+ `Synced organization name to Stripe: "${stripeCustomer.name}" → "${organization.name}"`,
100
+ );
101
+ }
102
+ } catch (e: any) {
103
+ ctx.logger.error(
104
+ `Failed to sync organization to Stripe: ${e.message}`,
105
+ );
106
+ }
107
+ };
108
+
109
+ /**
110
+ * Block deletion if organization has active subscriptions
111
+ */
112
+ const beforeDeleteStripeOrg = async (data: {
113
+ organization: Organization & WithStripeCustomerId;
114
+ user: User;
115
+ }) => {
116
+ const { organization } = data;
117
+ if (!organization.stripeCustomerId) return;
118
+
119
+ try {
120
+ // Check if organization has any active subscriptions
121
+ const subscriptions = await client.subscriptions.list({
122
+ customer: organization.stripeCustomerId,
123
+ status: "all",
124
+ limit: 100, // 1 ~ 100
125
+ });
126
+ for (const sub of subscriptions.data) {
127
+ if (
128
+ sub.status !== "canceled" &&
129
+ sub.status !== "incomplete" &&
130
+ sub.status !== "incomplete_expired"
131
+ ) {
132
+ throw APIError.from(
133
+ "BAD_REQUEST",
134
+ STRIPE_ERROR_CODES.ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION,
135
+ );
136
+ }
137
+ }
138
+ } catch (error: any) {
139
+ if (error instanceof APIError) {
140
+ throw error;
141
+ }
142
+ ctx.logger.error(
143
+ `Failed to check organization subscriptions: ${error.message}`,
144
+ );
145
+ throw error;
146
+ }
147
+ };
148
+
149
+ orgPlugin.options.organizationHooks = {
150
+ ...existingHooks,
151
+ afterUpdateOrganization: existingHooks.afterUpdateOrganization
152
+ ? async (data) => {
153
+ await existingHooks.afterUpdateOrganization!(data);
154
+ await afterUpdateStripeOrg(data);
155
+ }
156
+ : afterUpdateStripeOrg,
157
+ beforeDeleteOrganization: existingHooks.beforeDeleteOrganization
158
+ ? async (data) => {
159
+ await existingHooks.beforeDeleteOrganization!(data);
160
+ await beforeDeleteStripeOrg(data);
161
+ }
162
+ : beforeDeleteStripeOrg,
163
+ };
164
+ }
165
+
62
166
  return {
63
167
  options: {
64
168
  databaseHooks: {
65
169
  user: {
66
170
  create: {
67
- async after(user, ctx) {
68
- if (!ctx || !options.createCustomerOnSignUp) return;
171
+ async after(user: User & WithStripeCustomerId, ctx) {
172
+ if (
173
+ !ctx ||
174
+ !options.createCustomerOnSignUp ||
175
+ user.stripeCustomerId // Skip if user already has a Stripe customer ID
176
+ ) {
177
+ return;
178
+ }
69
179
 
70
180
  try {
71
- const userWithStripe = user as typeof user & {
72
- stripeCustomerId?: string;
73
- };
74
-
75
- // Skip if user already has a Stripe customer ID
76
- if (userWithStripe.stripeCustomerId) return;
77
-
78
- // Check if customer already exists in Stripe by email
79
- const existingCustomers = await client.customers.list({
80
- email: user.email,
181
+ // Check if user customer already exists in Stripe by email
182
+ const existingCustomers = await client.customers.search({
183
+ query: `email:"${escapeStripeSearchValue(user.email)}" AND -metadata["customerType"]:"organization"`,
81
184
  limit: 1,
82
185
  });
83
186
 
84
187
  let stripeCustomer = existingCustomers.data[0];
85
188
 
86
- // If customer exists, link it to prevent duplicate creation
189
+ // If user customer exists, link it to prevent duplicate creation
87
190
  if (stripeCustomer) {
88
191
  await ctx.context.internalAdapter.updateUser(user.id, {
89
192
  stripeCustomerId: stripeCustomer.id,
@@ -120,6 +223,7 @@ export const stripe = <O extends StripeOptions>(options: O) => {
120
223
  name: user.name,
121
224
  metadata: {
122
225
  userId: user.id,
226
+ customerType: "user",
123
227
  },
124
228
  },
125
229
  extraCreateParams,
@@ -150,41 +254,34 @@ export const stripe = <O extends StripeOptions>(options: O) => {
150
254
  },
151
255
  },
152
256
  update: {
153
- async after(user, ctx) {
154
- if (!ctx) return;
257
+ async after(user: User & WithStripeCustomerId, ctx) {
258
+ if (
259
+ !ctx ||
260
+ !user.stripeCustomerId // Only proceed if user has a Stripe customer ID
261
+ )
262
+ return;
155
263
 
156
264
  try {
157
- // Cast user to include stripeCustomerId (added by the stripe plugin schema)
158
- const userWithStripe = user as typeof user & {
159
- stripeCustomerId?: string;
160
- };
161
-
162
- // Only proceed if user has a Stripe customer ID
163
- if (!userWithStripe.stripeCustomerId) return;
164
-
165
265
  // Get the user from the database to check if email actually changed
166
266
  // The 'user' parameter here is the freshly updated user
167
267
  // We need to check if the Stripe customer's email matches
168
268
  const stripeCustomer = await client.customers.retrieve(
169
- userWithStripe.stripeCustomerId,
269
+ user.stripeCustomerId,
170
270
  );
171
271
 
172
272
  // Check if customer was deleted
173
273
  if (stripeCustomer.deleted) {
174
274
  ctx.context.logger.warn(
175
- `Stripe customer ${userWithStripe.stripeCustomerId} was deleted, cannot update email`,
275
+ `Stripe customer ${user.stripeCustomerId} was deleted, cannot update email`,
176
276
  );
177
277
  return;
178
278
  }
179
279
 
180
280
  // If Stripe customer email doesn't match the user's current email, update it
181
281
  if (stripeCustomer.email !== user.email) {
182
- await client.customers.update(
183
- userWithStripe.stripeCustomerId,
184
- {
185
- email: user.email,
186
- },
187
- );
282
+ await client.customers.update(user.stripeCustomerId, {
283
+ email: user.email,
284
+ });
188
285
  ctx.context.logger.info(
189
286
  `Updated Stripe customer email from ${stripeCustomer.email} to ${user.email}`,
190
287
  );