`.retrieve()` calls should accept the object as first argument and allow reusing existing expansions · Issue #2305 · stripe/stripe-node · GitHub
Skip to content

.retrieve() calls should accept the object as first argument and allow reusing existing expansions #2305

Open

Description

@thexeos

Is your feature request related to a problem? Please describe.

It is common that objects being passed around come expanded (e.g., webhook handler had to expand the object for routing purposes), but that can't be narrowed down with types, so boilerplate (or helper functions) are inevitable. For example:

async function getChargePaymentIntent(charge: Stripe.Charge): Promise<Stripe.PaymentIntent | null> {
  return typeof charge.payment_intent === 'string'
    ? await stripe.paymentIntents.retrieve(charge.payment_intent)
    : charge.payment_intent
}

Describe the solution you'd like

It would be great if all retrieve() methods could accept the object (we'll skip over null - developer can handle that before making SDK calls), read id property on it and perform the fetch:

async function getChargePaymentIntent(charge: Stripe.Charge): Promise<Stripe.PaymentIntent> {
  return await stripe.paymentIntents.retrieve(charge.payment_intent)
}

Of course, if the object is already expanded we may be making an extra API call when data is already available, but most of my code is doing something like this:

async function getChargeSubscription(charge: Stripe.Charge): Promise<Stripe.Subscription | null> {
  return (await stripe.paymentIntents.retrieve(charge.payment_intent, {
    expand: ['invoice.subscription'],
  }))?.invoice?.subscription ?? null
}

So API will be called regardless. To avoid needless API calls, maybe retrieve could (optionally) return the object if it already matches the requested "shape" (aka with everything in expand already expanded:

async function getChargeInvoiceWithSubscription(charge: Stripe.Charge): Promise<Stripe.Invoice | null> {
  const paymentIntent = await stripe.paymentIntents.retrieve(charge.payment_intent, {
    expand: ['invoice.subscription'],
  }) // 1 API call is made, assuming `charge` has nothing expanded, yet

  if (!paymentIntent?.invoice) {
    return null
  }

  return await stripe.invoices.retrieve(paymentIntent.invoice, {
    expand: ['subscription'],
    staleOkay: true, // new parameter
  }) // 0 API calls are made, since `subscription` was expanded before and is already available
}

Additional context

#1793 brings up a related pain point, where reading IDs means writing boilerplate code (or using helper functions).

Activity

  1. xavdid-stripe commented on Apr 18, 2025

    @xavdid-stripe
    Contributor

    Hey @thexeos, thanks for the suggestion! Better handling for the types of expanded resources could be useful.

    We're doing some work to improve common paths soon. I can't promise these changes will be part of it, but I'll keep this use case in mind while we work. If you (or any other reader) has additional suggestions, please don't hesitate to leave a comment!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions