stripe.customers.retrieve causing issues as it is now returning Stripe.Customer | Stripe.DeletedCustomer
Asked Answered
P

5

8

I am upgrading to the latest Stripe API version (2020-03-02) and I am not sure how to access the values on my customer object as it now just shows the union of the properties between Stripe.Customer and Stripe.DeletedCustomer.

Any tips on how to check the type and convert to a Stripe.Customer type?

I am getting the following error:

Property 'metadata' does not exist on type 'Customer | DeletedCustomer'. Property 'metadata' does not exist on type 'DeletedCustomer'.

 const customer: Stripe.Customer | Stripe.DeletedCustomer = await stripe.customers.retrieve(customerId);
 const uid = customer.metadata.firebaseUID;

Note that Customer and DeletedCustomer are interfaces:

namespace Stripe {
     interface DeletedCustomer {
          id: string;
          object: 'customer';
          deleted: true;
        }
    
     interface Customer {
          id: string;
          metadata: Metadata;
    ...
    }
}
Prudential answered 29/6, 2020 at 3:3 Comment(3)
Why don't you just check if customer is a Stripe.Customer before running the line that accesses the metadata?Aphelion
@ddcastrodd how can I do that? I tried typeof and instanceof with no luck. Still relatively new to typescript.Prudential
I actually just used the following code before that line and it now doesn't throw an error and compiles: if (customer.deleted) { // Customer is deleted // TODO: Throw error return; }Prudential
A
6

I found this comment for discriminating between Stripe.Customer and Stripe.DeletedCustomer.

So this worked for me:

if (customer.deleted === true) {
  // TypeScript treats customer as Stripe.DeletedCustomer
} else {
  // TypeScript treats customer as Stripe.Customer
}

I originally tried just if (customer.deleted), but that didn't work. TypeScript only discriminated after I added the === true.

Aideaidedecamp answered 23/9, 2022 at 14:40 Comment(2)
ily, idk why this happened, like isn't it a boolean and equivilant? so weirdLayout
if (customer.deleted) does work as of typeScript 5.0Exuviate
T
1

Stripe provides a common deleted flag that you can use to determine if you have a Customer or DeletedCustomer.

// You don't need the explicit type, it's inferred
const customer = await stripe.customers.retrieve(customerId);
if (!customer.deleted) {
   const uid = customer.metadata.firebaseUID;
   ...
}

See this GitHub issue for more information.

Trimolecular answered 23/2, 2022 at 11:9 Comment(1)
Do you know this this requires a TypeScript configuration option? I tried this and it didn't work. See here for more details: #72956963Lockman
C
0

For differentiating types, you can:

  1. Check if a not shared property exists in the needed object,
  2. (or/and) Compare a/this property value

In your case, the Stripe.DeletedCustomer not have the metadata property, you can therefore simply verify that this property exists, if so then the "customer" is of type "Stripe.Customer" else is of type "Stripe.DeletedCustomer":

const customer: Stripe.Customer | Stripe.DeletedCustomer = await stripe.customers.retrieve(customerId);

if(customer instanceof Object && "metadata" in customer) {
 // $customer is of type "Stripe.Customer"
 const uid = customer.metadata.firebaseUID;
}
else {
 // $customer is of type "Stripe.DeletedCustomer
}

@see: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types

Coopery answered 25/11, 2020 at 22:24 Comment(2)
I couldn't get that to work. I also tried the below with no luck. const subscription: Stripe.Subscription | string = await stripe.subscriptions.retrieve( checkoutSession.subscription, { expand: ['latest_invoice'], stripeAccount: ${connectedStripeAccountId}, }); let invoice: Stripe.Invoice; // if(subscription instanceof Object && "latest_invoice" in subscription) { if((typeof subscription) === 'string') { return; } else invoice = subscription.latest_invoice; }Prudential
I get the error "Property 'latest_invoice' does not exist on type 'string'"Prudential
P
0

I had to do this but it is not a good solution as I need the "as any". There has to be a better way to do this.

const customer: string | Stripe.Customer | Stripe.DeletedCustomer = checkoutSession.customer;
  let stripeCustomerId: string;
  if (customer instanceof Object && "email" in customer) {
      stripeCustomerId = customer.id;
  } else if (customer instanceof String) { 
    // customer is id
    stripeCustomerId = (customer as any).id;
  } else {
    // customer is of type "Stripe.DeletedCustomer
    stripeCustomerId = null;
  } 
Prudential answered 2/11, 2022 at 19:38 Comment(0)
R
0

I used a cast to solve this problem :

const customer = await stripe.customers.retrieve(stripeId) as Stripe.Customer
Radiotelephony answered 24/4 at 14:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.