Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripe.Event.Data is too untyped #1387

Closed
saiichihashimoto opened this issue Mar 30, 2022 · 6 comments
Closed

Stripe.Event.Data is too untyped #1387

saiichihashimoto opened this issue Mar 30, 2022 · 6 comments
Labels
future type definition Marks issue related to outdated or incorrect TypeScript type definition

Comments

@saiichihashimoto
Copy link

Currently, when doing stripe.webhooks.constructEvent or stripe.webhooks.constructEventAsync, the event data (which is what we want) is fully untyped. This forces lots of casting, although the type of the event correlates directly with specific stripe objects. We can actually type Stripe.Event so that narrowing event.type (ie, event.type === "payment_intent.succeeded") also narrows the event.data.object and event.data.previous_attributes (for the previous example, Stripe.PaymentIntent).

I've been using this in my codebase and it's helped tremendously. I haven't been able to use a typescript declaration to override Stripe.Event since declarations union with existing definitions, which keeps Stripe.Event.Data.Object as any no matter what. Can we have the official types use this?

import type Stripe from "stripe";

type WebhookObjects = {
  // https://stripe.com/docs/api/events/types
  [EventName in Exclude<
    Stripe.WebhookEndpointCreateParams.EnabledEvent,
    "*"
  >]: EventName extends `account.application.${string}`
    ? Stripe.Application
    : EventName extends `account.external_account.${string}`
    ? Stripe.Card | Stripe.BankAccount
    : EventName extends `account.${string}`
    ? Stripe.Account
    : EventName extends `application_fee.refund.${string}`
    ? Stripe.FeeRefund
    : EventName extends `application_fee.${string}`
    ? Stripe.ApplicationFee
    : EventName extends `balance.${string}`
    ? Stripe.Balance
    : EventName extends `billing_portal.configuration.${string}`
    ? Stripe.BillingPortal.Configuration
    : EventName extends `capability.${string}`
    ? Stripe.Capability
    : EventName extends `charge.dispute.${string}`
    ? Stripe.Dispute
    : EventName extends `charge.refund.${string}`
    ? Stripe.Refund
    : EventName extends `charge.${string}`
    ? Stripe.Charge
    : EventName extends `checkout.session.${string}`
    ? Stripe.Checkout.Session
    : EventName extends `coupon.${string}`
    ? Stripe.Coupon
    : EventName extends `credit_note.${string}`
    ? Stripe.CreditNote
    : EventName extends `customer.discount.${string}`
    ? Stripe.Discount
    : EventName extends `customer.source.${string}`
    ? Stripe.Discount
    : EventName extends `customer.subscription.${string}`
    ? Stripe.Subscription
    : EventName extends `customer.tax_id.${string}`
    ? Stripe.TaxId
    : EventName extends `customer.${string}`
    ? Stripe.Customer
    : EventName extends `file.${string}`
    ? Stripe.File
    : EventName extends `identity.verification_session.${string}`
    ? Stripe.Identity.VerificationSession
    : EventName extends `invoice.${string}`
    ? Stripe.Invoice
    : EventName extends `invoiceitem.${string}`
    ? Stripe.InvoiceItem
    : EventName extends `issuing_authorization.${string}`
    ? Stripe.Issuing.Authorization
    : EventName extends `issuing_card.${string}`
    ? Stripe.Issuing.Card
    : EventName extends `issuing_cardholder.${string}`
    ? Stripe.Issuing.Cardholder
    : EventName extends `issuing_dispute.${string}`
    ? Stripe.Issuing.Dispute
    : EventName extends `issuing_transaction.${string}`
    ? Stripe.Issuing.Transaction
    : EventName extends `linked_account.${string}`
    ? Stripe.AccountLink
    : EventName extends `mandate.${string}`
    ? Stripe.Mandate
    : EventName extends `order.${string}`
    ? Stripe.Order
    : EventName extends `order_return.${string}`
    ? Stripe.OrderReturn
    : EventName extends `payment_intent.${string}`
    ? Stripe.PaymentIntent
    : EventName extends `payment_link.${string}`
    ? Stripe.PaymentLink
    : EventName extends `payment_method.${string}`
    ? Stripe.PaymentMethod
    : EventName extends `payout.${string}`
    ? Stripe.Payout
    : EventName extends `person.${string}`
    ? Stripe.Person
    : EventName extends `plan.${string}`
    ? Stripe.Plan
    : EventName extends `price.${string}`
    ? Stripe.Price
    : EventName extends `product.${string}`
    ? Stripe.Product
    : EventName extends `promotion_code.${string}`
    ? Stripe.PromotionCode
    : EventName extends `quote.${string}`
    ? Stripe.Quote
    : EventName extends `radar.early_fraud_warning.${string}`
    ? Stripe.Radar.EarlyFraudWarning
    : EventName extends `recipient.${string}`
    ? Stripe.Recipient
    : EventName extends `reporting.report_run.${string}`
    ? Stripe.Reporting.ReportRun
    : EventName extends `reporting.report_type.${string}`
    ? Stripe.Reporting.ReportType
    : EventName extends `review.${string}`
    ? Stripe.Review
    : EventName extends `setup_intent.${string}`
    ? Stripe.SetupIntent
    : EventName extends `sigma.scheduled_query_run.${string}`
    ? Stripe.Sigma.ScheduledQueryRun
    : EventName extends `sku.${string}`
    ? Stripe.Sku
    : EventName extends `source.transaction.${string}`
    ? Stripe.SourceTransaction
    : EventName extends `source.${string}`
    ? Stripe.Source
    : EventName extends `subscription_schedule.${string}`
    ? Stripe.SubscriptionSchedule
    : EventName extends `tax_rate.${string}`
    ? Stripe.TaxRate
    : EventName extends `topup.${string}`
    ? Stripe.Topup
    : EventName extends `transfer.${string}`
    ? Stripe.Transfer
    : never;
};

export type StripeEvent = Omit<Stripe.Event, "data"> &
  {
    [EventName in keyof WebhookObjects]: {
      // Stripe.Event.Data.Object is fully untyped. This will let us type narrow the type of data.object by type.
      data: EventName extends `${string}.updated`
        ? {
            object: WebhookObjects[EventName];
            previous_attributes: Partial<WebhookObjects[EventName]>;
          }
        : {
            object: WebhookObjects[EventName];
          };
      type: EventName;
    };
  }[keyof WebhookObjects];
@dcr-stripe
Copy link
Contributor

Hey @saiichihashimoto, thanks for the feedback!

We totally hear you on this. This is something we're investigating in order to provide the best typed experience we can.

Currently this is somewhat tricky because you have to consider the API version for the webhook and the SDK version for the types, but I think we can get to a good solution.

Going to label this as future given we are exploring it.

@saiichihashimoto
Copy link
Author

That's fair! I still think some intermediate would be good, event if Stripe.Event.Data was just a union of all of those types. Currently there's no version of narrowing types, the only path is forced casting. I'm wondering if there's an iterative path that leads to typing this based on all the factors.

@saiichihashimoto
Copy link
Author

Can we use the API version/SDK version as part of a ternary in here, where if it's the current version onwards we use these new types, but if it's before that, we stick with whatever types we had previously? I have a feeling that new users will use the newest version (hence these types being helpful!) and users of the older versions are already handling the previous types, so adding more strict types might actually be a breaking change.

@kamil-stripe kamil-stripe added the type definition Marks issue related to outdated or incorrect TypeScript type definition label Apr 20, 2022
@kamil-stripe
Copy link
Contributor

Related issues: #758 and #902

@jameshfisher
Copy link

Duplicate of #758?

@kamil-stripe
Copy link
Contributor

To keep things keep I'm going to close this as a duplicate. Please track related work in #758.

@kamil-stripe kamil-stripe closed this as not planned Won't fix, can't repro, duplicate, stale Jul 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
future type definition Marks issue related to outdated or incorrect TypeScript type definition
Projects
None yet
Development

No branches or pull requests

4 participants