-
Notifications
You must be signed in to change notification settings - Fork 245
Expand file tree
/
Copy pathhandleStripeCheckoutSessionExpired.ts
More file actions
70 lines (63 loc) · 2.09 KB
/
Copy pathhandleStripeCheckoutSessionExpired.ts
File metadata and controls
70 lines (63 loc) · 2.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { CusProductStatus } from "@autumn/shared";
import type Stripe from "stripe";
import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext";
import {
expireCustomerProducts,
expirePendingCustomerProducts,
} from "@/internal/billing/v2/execute/expirePendingCustomerProducts";
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService";
import { MetadataService } from "@/internal/metadata/MetadataService";
/**
* checkout.session.expired handler — cleans up cusProduct rows that were
* pre-inserted under the enable_plan_immediately flow but never got their
* subscription linked because the customer abandoned the checkout.
*
* Identifies rows by stripe_checkout_session_id. Skips any row that has
* subscription_ids populated (already completed via the success path).
*/
export const handleStripeCheckoutSessionExpired = async ({
ctx,
event,
}: {
ctx: StripeWebhookContext;
event: Stripe.CheckoutSessionExpiredEvent;
}) => {
const session = event.data.object;
const cusProducts = await CusProductService.getByStripeCheckoutSessionId({
db: ctx.db,
stripeCheckoutSessionId: session.id,
orgId: ctx.org.id,
env: ctx.env,
});
if (cusProducts.length === 0) {
// Try to clean up the metadata row even if no cusProduct ever got created
// (e.g. a deferred-flow checkout that expired).
if (session.metadata?.autumn_metadata_id) {
await expirePendingCustomerProducts({
ctx,
metadataId: session.metadata.autumn_metadata_id,
});
await MetadataService.delete({
db: ctx.db,
id: session.metadata.autumn_metadata_id,
});
}
return;
}
const abandonedCusProducts = cusProducts.filter(
(cusProduct) => (cusProduct.subscription_ids ?? []).length === 0,
);
await expireCustomerProducts({
ctx,
customerProducts: abandonedCusProducts,
});
if (session.metadata?.autumn_metadata_id) {
await MetadataService.delete({
db: ctx.db,
id: session.metadata.autumn_metadata_id,
});
}
ctx.logger.info(
`[checkout.session.expired] Expired ${cusProducts.length} cusProduct(s) linked to ${session.id}`,
);
};