-
Notifications
You must be signed in to change notification settings - Fork 823
Description
Describe the bug
I'm creating a new issue under the bug category because I don't think #1556 really captured how irritating this is. It makes using this otherwise fantastic package very tedious.
When you pass the expand
property to expand API response data, TypeScript typings are unnaffected, leaving it up to the user to augment the stripe types manually.
To Reproduce
Let's say I want to retrieve the stripe checkout session to simply display a checkout success page. In plain JavaScript it's as simple as this:
const session = await stripe.checkout.sessions.retrieve(checkoutSessionId, {
expand: ["subscription", "subscription.items.data.product"]
});
Now our session will have the full subscription object and our subscription items will include the associated price AND the product associated with that price.
However, in TypeScript the type of session.subscription
is string | Stripe.Subscription | null
. Because string
is still a possibility I cannot use session.subscription
directly. Instead I have to assert the type at every point.
const subscription = session.subscription as Stripe.Subscription | null;
Doesn't seem too bad for a top-level property, but it quickly gets out of hand:
const productName = ((session.subscription as Stripe.Subscription | null)?.items.data[0].price.product as Stripe.Product | null)?.name;
Capturing and asserting the type at every level gets extremely irritating and unreadable so you then end up having to dig deep into the Stripe type tree and create your own expanded types. Still pretty hard to look at, but at least the code is readable again:
type ExpandedSession = Stripe.Checkout.Session & {
subscription: Stripe.Subscription & {
items: {
data: Stripe.SubscriptionItem & {
price: Stripe.Price & {
product: Stripe.Product | null;
};
};
};
} | null;
};
const session = (await stripe.checkout.sessions.retrieve(checkoutSessionId, {
expand: ["subscription", "subscription.items.data.product"]
})) as Stripe.Response<ExpandedSession>;
All that work just to eliminate string
as a possibility on fields we know we've expanded and can only be the actual object or null
. I've never used a package that made managing types this tedious. It's so annoying and so unlike any other well-typed packages I've ever used that I think it deserves to be documented as a bug. Feel free to disagree but I have a hard time believing I'm the only one that finds this such a chore. I shudder to think how many devs are doing as any
while using this package 😢
Expected behavior
It is possible to dynamically narrow types within a package based on specified options passed in. I'm no TypeScript expert but I know enough to know that it is indeed possible to narrow the type down to Stripe.Subscription | null
, eliminating the string
union altogether, based on whether or not the expand
option was specified. It's also possible to strongly type the property path strings that you pass to expand
based on the other types within the package, making it so a user can only specify actual supported object paths.
While we're at it I'll also point out how unusual it is that we can't just import type { Product } from "stripe"
and we are instead forced to reference the types from the top-level Stripe
type.
Please bring on a TypeScript guru to give stripe-node a typings overhaul.
Code snippets
OS
Arch
Node version
v22.15.1
Library version
v18.1.0
API version
2025-04-30.basil
Additional context
No response