@better-auth/stripe 1.5.0-beta.8 → 1.5.0
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/README.md +17 -0
- package/dist/client.d.mts +52 -171
- package/dist/client.mjs +3 -2
- package/dist/client.mjs.map +1 -0
- package/dist/{error-codes-Clj-xYDP.mjs → error-codes-CCosYkXx.mjs} +4 -1
- package/dist/error-codes-CCosYkXx.mjs.map +1 -0
- package/dist/index.d.mts +519 -2
- package/dist/index.mjs +586 -232
- package/dist/index.mjs.map +1 -0
- package/dist/types-OT6L84x4.d.mts +480 -0
- package/package.json +28 -22
- package/.turbo/turbo-build.log +0 -17
- package/CHANGELOG.md +0 -24
- package/dist/index-BqGWQFAv.d.mts +0 -1015
- package/src/client.ts +0 -38
- package/src/error-codes.ts +0 -30
- package/src/hooks.ts +0 -435
- package/src/index.ts +0 -314
- package/src/middleware.ts +0 -104
- package/src/routes.ts +0 -1681
- package/src/schema.ts +0 -128
- package/src/types.ts +0 -449
- package/src/utils.ts +0 -70
- package/test/stripe-organization.test.ts +0 -1993
- package/test/stripe.test.ts +0 -4807
- package/tsconfig.json +0 -14
- package/tsdown.config.ts +0 -8
- package/vitest.config.ts +0 -8
package/src/client.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { BetterAuthClientPlugin } from "better-auth";
|
|
2
|
-
import { STRIPE_ERROR_CODES } from "./error-codes";
|
|
3
|
-
import type { stripe } from "./index";
|
|
4
|
-
|
|
5
|
-
export const stripeClient = <
|
|
6
|
-
O extends {
|
|
7
|
-
subscription: boolean;
|
|
8
|
-
},
|
|
9
|
-
>(
|
|
10
|
-
options?: O | undefined,
|
|
11
|
-
) => {
|
|
12
|
-
return {
|
|
13
|
-
id: "stripe-client",
|
|
14
|
-
$InferServerPlugin: {} as ReturnType<
|
|
15
|
-
typeof stripe<
|
|
16
|
-
O["subscription"] extends true
|
|
17
|
-
? {
|
|
18
|
-
stripeClient: any;
|
|
19
|
-
stripeWebhookSecret: string;
|
|
20
|
-
subscription: {
|
|
21
|
-
enabled: true;
|
|
22
|
-
plans: [];
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
: {
|
|
26
|
-
stripeClient: any;
|
|
27
|
-
stripeWebhookSecret: string;
|
|
28
|
-
}
|
|
29
|
-
>
|
|
30
|
-
>,
|
|
31
|
-
pathMethods: {
|
|
32
|
-
"/subscription/billing-portal": "POST",
|
|
33
|
-
"/subscription/restore": "POST",
|
|
34
|
-
},
|
|
35
|
-
$ERROR_CODES: STRIPE_ERROR_CODES,
|
|
36
|
-
} satisfies BetterAuthClientPlugin;
|
|
37
|
-
};
|
|
38
|
-
export * from "./error-codes";
|
package/src/error-codes.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
|
|
2
|
-
|
|
3
|
-
export const STRIPE_ERROR_CODES = defineErrorCodes({
|
|
4
|
-
UNAUTHORIZED: "Unauthorized access",
|
|
5
|
-
INVALID_REQUEST_BODY: "Invalid request body",
|
|
6
|
-
SUBSCRIPTION_NOT_FOUND: "Subscription not found",
|
|
7
|
-
SUBSCRIPTION_PLAN_NOT_FOUND: "Subscription plan not found",
|
|
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",
|
|
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",
|
|
17
|
-
FAILED_TO_FETCH_PLANS: "Failed to fetch plans",
|
|
18
|
-
EMAIL_VERIFICATION_REQUIRED:
|
|
19
|
-
"Email verification is required before you can subscribe to a plan",
|
|
20
|
-
SUBSCRIPTION_NOT_ACTIVE: "Subscription is not active",
|
|
21
|
-
SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION:
|
|
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",
|
|
30
|
-
});
|
package/src/hooks.ts
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import type { GenericEndpointContext } from "@better-auth/core";
|
|
2
|
-
import type { User } from "@better-auth/core/db";
|
|
3
|
-
import type { Organization } from "better-auth/plugins/organization";
|
|
4
|
-
import type Stripe from "stripe";
|
|
5
|
-
import type { CustomerType, StripeOptions, Subscription } from "./types";
|
|
6
|
-
import {
|
|
7
|
-
getPlanByPriceInfo,
|
|
8
|
-
isActiveOrTrialing,
|
|
9
|
-
isPendingCancel,
|
|
10
|
-
isStripePendingCancel,
|
|
11
|
-
} from "./utils";
|
|
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
|
-
|
|
39
|
-
export async function onCheckoutSessionCompleted(
|
|
40
|
-
ctx: GenericEndpointContext,
|
|
41
|
-
options: StripeOptions,
|
|
42
|
-
event: Stripe.Event,
|
|
43
|
-
) {
|
|
44
|
-
try {
|
|
45
|
-
const client = options.stripeClient;
|
|
46
|
-
const checkoutSession = event.data.object as Stripe.Checkout.Session;
|
|
47
|
-
if (checkoutSession.mode === "setup" || !options.subscription?.enabled) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const subscription = await client.subscriptions.retrieve(
|
|
51
|
-
checkoutSession.subscription as string,
|
|
52
|
-
);
|
|
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;
|
|
63
|
-
const plan = await getPlanByPriceInfo(
|
|
64
|
-
options,
|
|
65
|
-
priceId as string,
|
|
66
|
-
priceLookupKey,
|
|
67
|
-
);
|
|
68
|
-
if (plan) {
|
|
69
|
-
const referenceId =
|
|
70
|
-
checkoutSession?.client_reference_id ||
|
|
71
|
-
checkoutSession?.metadata?.referenceId;
|
|
72
|
-
const subscriptionId = checkoutSession?.metadata?.subscriptionId;
|
|
73
|
-
const seats = subscriptionItem.quantity;
|
|
74
|
-
if (referenceId && subscriptionId) {
|
|
75
|
-
const trial =
|
|
76
|
-
subscription.trial_start && subscription.trial_end
|
|
77
|
-
? {
|
|
78
|
-
trialStart: new Date(subscription.trial_start * 1000),
|
|
79
|
-
trialEnd: new Date(subscription.trial_end * 1000),
|
|
80
|
-
}
|
|
81
|
-
: {};
|
|
82
|
-
|
|
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,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (trial.trialStart && plan.freeTrial?.onTrialStart) {
|
|
114
|
-
await plan.freeTrial.onTrialStart(dbSubscription as Subscription);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!dbSubscription) {
|
|
118
|
-
dbSubscription = await ctx.context.adapter.findOne<Subscription>({
|
|
119
|
-
model: "subscription",
|
|
120
|
-
where: [
|
|
121
|
-
{
|
|
122
|
-
field: "id",
|
|
123
|
-
value: subscriptionId,
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
await options.subscription?.onSubscriptionComplete?.(
|
|
129
|
-
{
|
|
130
|
-
event,
|
|
131
|
-
subscription: dbSubscription as Subscription,
|
|
132
|
-
stripeSubscription: subscription,
|
|
133
|
-
plan,
|
|
134
|
-
},
|
|
135
|
-
ctx,
|
|
136
|
-
);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} catch (e: any) {
|
|
141
|
-
ctx.context.logger.error(`Stripe webhook failed. Error: ${e.message}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export async function onSubscriptionCreated(
|
|
146
|
-
ctx: GenericEndpointContext,
|
|
147
|
-
options: StripeOptions,
|
|
148
|
-
event: Stripe.Event,
|
|
149
|
-
) {
|
|
150
|
-
try {
|
|
151
|
-
if (!options.subscription?.enabled) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const subscriptionCreated = event.data.object as Stripe.Subscription;
|
|
156
|
-
const stripeCustomerId = subscriptionCreated.customer?.toString();
|
|
157
|
-
if (!stripeCustomerId) {
|
|
158
|
-
ctx.context.logger.warn(
|
|
159
|
-
`Stripe webhook warning: customer.subscription.created event received without customer ID`,
|
|
160
|
-
);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Check if subscription already exists in database
|
|
165
|
-
const subscriptionId = subscriptionCreated.metadata?.subscriptionId;
|
|
166
|
-
const existingSubscription =
|
|
167
|
-
await ctx.context.adapter.findOne<Subscription>({
|
|
168
|
-
model: "subscription",
|
|
169
|
-
where: subscriptionId
|
|
170
|
-
? [{ field: "id", value: subscriptionId }]
|
|
171
|
-
: [{ field: "stripeSubscriptionId", value: subscriptionCreated.id }], // Probably won't match since it's not set yet
|
|
172
|
-
});
|
|
173
|
-
if (existingSubscription) {
|
|
174
|
-
ctx.context.logger.info(
|
|
175
|
-
`Stripe webhook: Subscription already exists in database (id: ${existingSubscription.id}), skipping creation`,
|
|
176
|
-
);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Find reference
|
|
181
|
-
const reference = await findReferenceByStripeCustomerId(
|
|
182
|
-
ctx,
|
|
183
|
-
options,
|
|
184
|
-
stripeCustomerId,
|
|
185
|
-
);
|
|
186
|
-
if (!reference) {
|
|
187
|
-
ctx.context.logger.warn(
|
|
188
|
-
`Stripe webhook warning: No user or organization found with stripeCustomerId: ${stripeCustomerId}`,
|
|
189
|
-
);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const { referenceId, customerType } = reference;
|
|
193
|
-
|
|
194
|
-
const subscriptionItem = subscriptionCreated.items.data[0];
|
|
195
|
-
if (!subscriptionItem) {
|
|
196
|
-
ctx.context.logger.warn(
|
|
197
|
-
`Stripe webhook warning: Subscription ${subscriptionCreated.id} has no items`,
|
|
198
|
-
);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const priceId = subscriptionItem.price.id;
|
|
203
|
-
const priceLookupKey = subscriptionItem.price.lookup_key || null;
|
|
204
|
-
const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
|
|
205
|
-
if (!plan) {
|
|
206
|
-
ctx.context.logger.warn(
|
|
207
|
-
`Stripe webhook warning: No matching plan found for priceId: ${priceId}`,
|
|
208
|
-
);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const seats = subscriptionItem.quantity;
|
|
213
|
-
const periodStart = new Date(subscriptionItem.current_period_start * 1000);
|
|
214
|
-
const periodEnd = new Date(subscriptionItem.current_period_end * 1000);
|
|
215
|
-
|
|
216
|
-
const trial =
|
|
217
|
-
subscriptionCreated.trial_start && subscriptionCreated.trial_end
|
|
218
|
-
? {
|
|
219
|
-
trialStart: new Date(subscriptionCreated.trial_start * 1000),
|
|
220
|
-
trialEnd: new Date(subscriptionCreated.trial_end * 1000),
|
|
221
|
-
}
|
|
222
|
-
: {};
|
|
223
|
-
|
|
224
|
-
// Create the subscription in the database
|
|
225
|
-
const newSubscription = await ctx.context.adapter.create<Subscription>({
|
|
226
|
-
model: "subscription",
|
|
227
|
-
data: {
|
|
228
|
-
referenceId,
|
|
229
|
-
stripeCustomerId,
|
|
230
|
-
stripeSubscriptionId: subscriptionCreated.id,
|
|
231
|
-
status: subscriptionCreated.status,
|
|
232
|
-
plan: plan.name.toLowerCase(),
|
|
233
|
-
periodStart,
|
|
234
|
-
periodEnd,
|
|
235
|
-
seats,
|
|
236
|
-
...(plan.limits ? { limits: plan.limits } : {}),
|
|
237
|
-
...trial,
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
ctx.context.logger.info(
|
|
242
|
-
`Stripe webhook: Created subscription ${subscriptionCreated.id} for ${customerType} ${referenceId} from dashboard`,
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
await options.subscription.onSubscriptionCreated?.({
|
|
246
|
-
event,
|
|
247
|
-
subscription: newSubscription,
|
|
248
|
-
stripeSubscription: subscriptionCreated,
|
|
249
|
-
plan,
|
|
250
|
-
});
|
|
251
|
-
} catch (error: any) {
|
|
252
|
-
ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export async function onSubscriptionUpdated(
|
|
257
|
-
ctx: GenericEndpointContext,
|
|
258
|
-
options: StripeOptions,
|
|
259
|
-
event: Stripe.Event,
|
|
260
|
-
) {
|
|
261
|
-
try {
|
|
262
|
-
if (!options.subscription?.enabled) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const subscriptionUpdated = event.data.object as Stripe.Subscription;
|
|
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;
|
|
276
|
-
const plan = await getPlanByPriceInfo(options, priceId, priceLookupKey);
|
|
277
|
-
|
|
278
|
-
const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
|
|
279
|
-
const customerId = subscriptionUpdated.customer?.toString();
|
|
280
|
-
let subscription = await ctx.context.adapter.findOne<Subscription>({
|
|
281
|
-
model: "subscription",
|
|
282
|
-
where: subscriptionId
|
|
283
|
-
? [{ field: "id", value: subscriptionId }]
|
|
284
|
-
: [{ field: "stripeSubscriptionId", value: subscriptionUpdated.id }],
|
|
285
|
-
});
|
|
286
|
-
if (!subscription) {
|
|
287
|
-
const subs = await ctx.context.adapter.findMany<Subscription>({
|
|
288
|
-
model: "subscription",
|
|
289
|
-
where: [{ field: "stripeCustomerId", value: customerId }],
|
|
290
|
-
});
|
|
291
|
-
if (subs.length > 1) {
|
|
292
|
-
const activeSub = subs.find((sub: Subscription) =>
|
|
293
|
-
isActiveOrTrialing(sub),
|
|
294
|
-
);
|
|
295
|
-
if (!activeSub) {
|
|
296
|
-
ctx.context.logger.warn(
|
|
297
|
-
`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no active subscription is found`,
|
|
298
|
-
);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
subscription = activeSub;
|
|
302
|
-
} else {
|
|
303
|
-
subscription = subs[0]!;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const updatedSubscription = await ctx.context.adapter.update<Subscription>({
|
|
308
|
-
model: "subscription",
|
|
309
|
-
update: {
|
|
310
|
-
...(plan
|
|
311
|
-
? {
|
|
312
|
-
plan: plan.name.toLowerCase(),
|
|
313
|
-
limits: plan.limits,
|
|
314
|
-
}
|
|
315
|
-
: {}),
|
|
316
|
-
updatedAt: new Date(),
|
|
317
|
-
status: subscriptionUpdated.status,
|
|
318
|
-
periodStart: new Date(subscriptionItem.current_period_start * 1000),
|
|
319
|
-
periodEnd: new Date(subscriptionItem.current_period_end * 1000),
|
|
320
|
-
cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
|
|
321
|
-
cancelAt: subscriptionUpdated.cancel_at
|
|
322
|
-
? new Date(subscriptionUpdated.cancel_at * 1000)
|
|
323
|
-
: null,
|
|
324
|
-
canceledAt: subscriptionUpdated.canceled_at
|
|
325
|
-
? new Date(subscriptionUpdated.canceled_at * 1000)
|
|
326
|
-
: null,
|
|
327
|
-
endedAt: subscriptionUpdated.ended_at
|
|
328
|
-
? new Date(subscriptionUpdated.ended_at * 1000)
|
|
329
|
-
: null,
|
|
330
|
-
seats: subscriptionItem.quantity,
|
|
331
|
-
stripeSubscriptionId: subscriptionUpdated.id,
|
|
332
|
-
},
|
|
333
|
-
where: [
|
|
334
|
-
{
|
|
335
|
-
field: "id",
|
|
336
|
-
value: subscription.id,
|
|
337
|
-
},
|
|
338
|
-
],
|
|
339
|
-
});
|
|
340
|
-
const isNewCancellation =
|
|
341
|
-
subscriptionUpdated.status === "active" &&
|
|
342
|
-
isStripePendingCancel(subscriptionUpdated) &&
|
|
343
|
-
!isPendingCancel(subscription);
|
|
344
|
-
if (isNewCancellation) {
|
|
345
|
-
await options.subscription.onSubscriptionCancel?.({
|
|
346
|
-
subscription,
|
|
347
|
-
cancellationDetails:
|
|
348
|
-
subscriptionUpdated.cancellation_details || undefined,
|
|
349
|
-
stripeSubscription: subscriptionUpdated,
|
|
350
|
-
event,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
await options.subscription.onSubscriptionUpdate?.({
|
|
354
|
-
event,
|
|
355
|
-
subscription: updatedSubscription || subscription,
|
|
356
|
-
});
|
|
357
|
-
if (plan) {
|
|
358
|
-
if (
|
|
359
|
-
subscriptionUpdated.status === "active" &&
|
|
360
|
-
subscription.status === "trialing" &&
|
|
361
|
-
plan.freeTrial?.onTrialEnd
|
|
362
|
-
) {
|
|
363
|
-
await plan.freeTrial.onTrialEnd({ subscription }, ctx);
|
|
364
|
-
}
|
|
365
|
-
if (
|
|
366
|
-
subscriptionUpdated.status === "incomplete_expired" &&
|
|
367
|
-
subscription.status === "trialing" &&
|
|
368
|
-
plan.freeTrial?.onTrialExpired
|
|
369
|
-
) {
|
|
370
|
-
await plan.freeTrial.onTrialExpired(subscription, ctx);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
} catch (error: any) {
|
|
374
|
-
ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export async function onSubscriptionDeleted(
|
|
379
|
-
ctx: GenericEndpointContext,
|
|
380
|
-
options: StripeOptions,
|
|
381
|
-
event: Stripe.Event,
|
|
382
|
-
) {
|
|
383
|
-
if (!options.subscription?.enabled) {
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
try {
|
|
387
|
-
const subscriptionDeleted = event.data.object as Stripe.Subscription;
|
|
388
|
-
const subscriptionId = subscriptionDeleted.id;
|
|
389
|
-
const subscription = await ctx.context.adapter.findOne<Subscription>({
|
|
390
|
-
model: "subscription",
|
|
391
|
-
where: [
|
|
392
|
-
{
|
|
393
|
-
field: "stripeSubscriptionId",
|
|
394
|
-
value: subscriptionId,
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
});
|
|
398
|
-
if (subscription) {
|
|
399
|
-
await ctx.context.adapter.update({
|
|
400
|
-
model: "subscription",
|
|
401
|
-
where: [
|
|
402
|
-
{
|
|
403
|
-
field: "id",
|
|
404
|
-
value: subscription.id,
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
update: {
|
|
408
|
-
status: "canceled",
|
|
409
|
-
updatedAt: new Date(),
|
|
410
|
-
cancelAtPeriodEnd: subscriptionDeleted.cancel_at_period_end,
|
|
411
|
-
cancelAt: subscriptionDeleted.cancel_at
|
|
412
|
-
? new Date(subscriptionDeleted.cancel_at * 1000)
|
|
413
|
-
: null,
|
|
414
|
-
canceledAt: subscriptionDeleted.canceled_at
|
|
415
|
-
? new Date(subscriptionDeleted.canceled_at * 1000)
|
|
416
|
-
: null,
|
|
417
|
-
endedAt: subscriptionDeleted.ended_at
|
|
418
|
-
? new Date(subscriptionDeleted.ended_at * 1000)
|
|
419
|
-
: null,
|
|
420
|
-
},
|
|
421
|
-
});
|
|
422
|
-
await options.subscription.onSubscriptionDeleted?.({
|
|
423
|
-
event,
|
|
424
|
-
stripeSubscription: subscriptionDeleted,
|
|
425
|
-
subscription,
|
|
426
|
-
});
|
|
427
|
-
} else {
|
|
428
|
-
ctx.context.logger.warn(
|
|
429
|
-
`Stripe webhook error: Subscription not found for subscriptionId: ${subscriptionId}`,
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
} catch (error: any) {
|
|
433
|
-
ctx.context.logger.error(`Stripe webhook failed. Error: ${error}`);
|
|
434
|
-
}
|
|
435
|
-
}
|