diff --git a/.rubocop.yml b/.rubocop.yml index a12b40ef2..529b2a888 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -18,6 +18,7 @@ Layout/LineLength: Exclude: - "lib/stripe/object_types.rb" - "lib/stripe/stripe_client.rb" + - "lib/stripe/stripe_event_router.rb" - "lib/stripe/resources/**/*.rb" - "lib/stripe/services/**/*.rb" - "lib/stripe/events/**/*.rb" @@ -86,6 +87,10 @@ Metrics/ParameterLists: - "lib/stripe/stripe_client.rb" - "lib/stripe/params/**/*.rb" +Naming/MethodName: + Exclude: + - "lib/stripe/stripe_event_router.rb" + Naming/MethodParameterName: # We have many parameters that are less than 3 characters for tax codes Exclude: diff --git a/lib/stripe.rb b/lib/stripe.rb index 7c4559bb5..d49c21d2e 100644 --- a/lib/stripe.rb +++ b/lib/stripe.rb @@ -51,6 +51,7 @@ require "stripe/api_resource_test_helpers" require "stripe/singleton_api_resource" require "stripe/webhook" +require "stripe/stripe_event_router" require "stripe/stripe_configuration" require "stripe/resources/v2/amount" require "stripe/resources/v2/deleted_object" diff --git a/lib/stripe/stripe_client.rb b/lib/stripe/stripe_client.rb index 5e4d7236c..8ce66489f 100644 --- a/lib/stripe/stripe_client.rb +++ b/lib/stripe/stripe_client.rb @@ -8,9 +8,12 @@ class StripeClient # attr_readers: The beginning of the section generated from our OpenAPI spec attr_reader :v1 attr_reader :v2 - # attr_readers: The end of the section generated from our OpenAPI spec + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + attr_reader :requestor + # For internal use only. Does not provide a stable API and may be broken # with future non-major changes. CLIENT_OPTIONS = Set.new(%i[api_key stripe_account stripe_context api_version api_base uploads_base connect_base meter_events_base client_id]) @@ -87,5 +90,9 @@ def deserialize(data, api_mode: :v1) data = JSON.parse(data) if data.is_a?(String) Util.convert_to_stripe_object(data, {}, api_mode: api_mode, requestor: @requestor) end + + def router(webhook_secret, &blk) + ::Stripe::StripeEventRouter.new(self, webhook_secret, &blk) + end end end diff --git a/lib/stripe/stripe_event_router.rb b/lib/stripe/stripe_event_router.rb new file mode 100644 index 000000000..b479b423d --- /dev/null +++ b/lib/stripe/stripe_event_router.rb @@ -0,0 +1,411 @@ +# frozen_string_literal: true + +module Stripe + class UnhandledNotificationDetails + attr_reader :is_known_event_type + + def initialize(is_known_event_type) + @is_known_event_type = is_known_event_type + end + end + + class StripeEventRouter + def initialize(client, webhook_secret, &on_unhandled_handler) + raise ArgumentError, "You must pass a block to respond to unhandled events" if on_unhandled_handler.nil? + + @client = client + @webhook_secret = webhook_secret + @on_unhandled_handler = on_unhandled_handler + + @registered_handlers = {} + @has_handled_events = false + end + + def handle(webhook_body, sig_header) + @has_handled_events = true + + notif = @client.parse_event_notification( + webhook_body, + sig_header, + @webhook_secret + ) + + @handler = @registered_handlers[notif.type] + original_context = @client.requestor.config.stripe_context + @client.requestor.config.stripe_context = notif.context + begin + if @handler + @handler.call(notif, @client) + else + @on_unhandled_handler.call(notif, @client, + UnhandledNotificationDetails.new(!notif.is_a?(Stripe::Events::UnknownEventNotification))) + end + ensure + @client.requestor.config.stripe_context = original_context + end + end + + def registered_event_types + @registered_handlers.keys.sort + end + + private def register(event_type, &handler) + raise "Cannot register new event handlers after handling events" if @has_handled_events + if @registered_handlers.key?(event_type) + raise ArgumentError, "Handler already registered for event type: #{event_type}" + end + + @registered_handlers[event_type] = handler + end + + # def on_UnknownEventNotification(event_notification, _client) + # raise "Received event type that the SDK doesn't have a corresponding class for: \"#{event_notification.type}\". Consider upgrading your SDK to handle this more gracefully." + # end + + # event-handler-methods: The beginning of the section generated from our OpenAPI spec + def on_V1BillingMeterErrorReportTriggeredEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v1.billing.meter.error_report_triggered", &handler) + end + + def on_V1BillingMeterNoMeterFoundEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v1.billing.meter.no_meter_found", &handler) + end + + def on_V2CoreAccountClosedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account.closed", &handler) + end + + def on_V2CoreAccountCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account.created", &handler) + end + + def on_V2CoreAccountUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account.updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationCustomerCapabilityStatusUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.customer].capability_status_updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationCustomerUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.customer].updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationMerchantCapabilityStatusUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.merchant].capability_status_updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationMerchantUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.merchant].updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationRecipientCapabilityStatusUpdatedEventNotification( + &handler + ) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.recipient].capability_status_updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationRecipientUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.recipient].updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationStorerCapabilityStatusUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.storer].capability_status_updated", &handler) + end + + def on_V2CoreAccountIncludingConfigurationStorerUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[configuration.storer].updated", &handler) + end + + def on_V2CoreAccountIncludingDefaultsUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[defaults].updated", &handler) + end + + def on_V2CoreAccountIncludingIdentityUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[identity].updated", &handler) + end + + def on_V2CoreAccountIncludingRequirementsUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account[requirements].updated", &handler) + end + + def on_V2CoreAccountLinkReturnedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account_link.returned", &handler) + end + + def on_V2CoreAccountPersonCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account_person.created", &handler) + end + + def on_V2CoreAccountPersonDeletedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account_person.deleted", &handler) + end + + def on_V2CoreAccountPersonUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.account_person.updated", &handler) + end + + def on_V2CoreEventDestinationPingEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.event_destination.ping", &handler) + end + + def on_V2CoreHealthEventGenerationFailureResolvedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.core.health.event_generation_failure.resolved", &handler) + end + + def on_V2MoneyManagementAdjustmentCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.adjustment.created", &handler) + end + + def on_V2MoneyManagementFinancialAccountCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.financial_account.created", &handler) + end + + def on_V2MoneyManagementFinancialAccountUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.financial_account.updated", &handler) + end + + def on_V2MoneyManagementFinancialAddressActivatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.financial_address.activated", &handler) + end + + def on_V2MoneyManagementFinancialAddressFailedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.financial_address.failed", &handler) + end + + def on_V2MoneyManagementInboundTransferAvailableEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.inbound_transfer.available", &handler) + end + + def on_V2MoneyManagementInboundTransferBankDebitFailedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.inbound_transfer.bank_debit_failed", &handler) + end + + def on_V2MoneyManagementInboundTransferBankDebitProcessingEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.inbound_transfer.bank_debit_processing", &handler) + end + + def on_V2MoneyManagementInboundTransferBankDebitQueuedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.inbound_transfer.bank_debit_queued", &handler) + end + + def on_V2MoneyManagementInboundTransferBankDebitReturnedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.inbound_transfer.bank_debit_returned", &handler) + end + + def on_V2MoneyManagementInboundTransferBankDebitSucceededEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.inbound_transfer.bank_debit_succeeded", &handler) + end + + def on_V2MoneyManagementOutboundPaymentCanceledEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_payment.canceled", &handler) + end + + def on_V2MoneyManagementOutboundPaymentCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_payment.created", &handler) + end + + def on_V2MoneyManagementOutboundPaymentFailedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_payment.failed", &handler) + end + + def on_V2MoneyManagementOutboundPaymentPostedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_payment.posted", &handler) + end + + def on_V2MoneyManagementOutboundPaymentReturnedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_payment.returned", &handler) + end + + def on_V2MoneyManagementOutboundPaymentUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_payment.updated", &handler) + end + + def on_V2MoneyManagementOutboundTransferCanceledEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_transfer.canceled", &handler) + end + + def on_V2MoneyManagementOutboundTransferCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_transfer.created", &handler) + end + + def on_V2MoneyManagementOutboundTransferFailedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_transfer.failed", &handler) + end + + def on_V2MoneyManagementOutboundTransferPostedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_transfer.posted", &handler) + end + + def on_V2MoneyManagementOutboundTransferReturnedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_transfer.returned", &handler) + end + + def on_V2MoneyManagementOutboundTransferUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.outbound_transfer.updated", &handler) + end + + def on_V2MoneyManagementPayoutMethodUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.payout_method.updated", &handler) + end + + def on_V2MoneyManagementReceivedCreditAvailableEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_credit.available", &handler) + end + + def on_V2MoneyManagementReceivedCreditFailedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_credit.failed", &handler) + end + + def on_V2MoneyManagementReceivedCreditReturnedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_credit.returned", &handler) + end + + def on_V2MoneyManagementReceivedCreditSucceededEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_credit.succeeded", &handler) + end + + def on_V2MoneyManagementReceivedDebitCanceledEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_debit.canceled", &handler) + end + + def on_V2MoneyManagementReceivedDebitFailedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_debit.failed", &handler) + end + + def on_V2MoneyManagementReceivedDebitPendingEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_debit.pending", &handler) + end + + def on_V2MoneyManagementReceivedDebitSucceededEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_debit.succeeded", &handler) + end + + def on_V2MoneyManagementReceivedDebitUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.received_debit.updated", &handler) + end + + def on_V2MoneyManagementTransactionCreatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.transaction.created", &handler) + end + + def on_V2MoneyManagementTransactionUpdatedEventNotification(&handler) + raise ArgumentError, "Block required to register event handler" if handler.nil? + + register("v2.money_management.transaction.updated", &handler) + end + # event-handler-methods: The end of the section generated from our OpenAPI spec + end +end diff --git a/rbi/stripe/stripe_client.rbi b/rbi/stripe/stripe_client.rbi index 2dc68fa85..fd575ebf1 100644 --- a/rbi/stripe/stripe_client.rbi +++ b/rbi/stripe/stripe_client.rbi @@ -13,5 +13,18 @@ module Stripe .returns(::Stripe::V2::Core::EventNotification) end def parse_event_notification(payload, sig_header, secret, tolerance:); end + + sig do + params( + webhook_secret: String, + blk: T.proc.params( + event_notification: ::Stripe::V2::Core::EventNotification, + client: ::Stripe::StripeClient, + details: ::Stripe::UnhandledNotificationDetails + ).void + ) + .returns(::Stripe::StripeEventRouter) + end + def router(webhook_secret, &blk); end end end diff --git a/rbi/stripe/stripe_event_router.rbi b/rbi/stripe/stripe_event_router.rbi new file mode 100644 index 000000000..9b8bc3d78 --- /dev/null +++ b/rbi/stripe/stripe_event_router.rbi @@ -0,0 +1,381 @@ +# frozen_string_literal: true +# typed: true + +module Stripe + class UnhandledNotificationDetails + sig { returns(T::Boolean) } + def is_known_event_type; end + end + + class StripeEventRouter + sig do + params( + client: ::Stripe::StripeClient, + webhook_secret: String, + on_unhandled_handler: T.proc.params( + event_notification: ::Stripe::V2::Core::EventNotification, + client: ::Stripe::StripeClient, + details: ::Stripe::UnhandledNotificationDetails).void) + .void + end + def initialize(client, webhook_secret, &on_unhandled_handler); end + end + + sig do + params( + webhook_body: String, + sig_header: String + ) + .void + end + def handle(webhook_body, sig_header); end + + sig { returns(T::Array[String]) } + def registered_event_types; end + + # event-handler-methods: The beginning of the section generated from our OpenAPI spec + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V1BillingMeterErrorReportTriggeredEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V1BillingMeterNoMeterFoundEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V1BillingMeterNoMeterFoundEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountClosedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountClosedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationCustomerCapabilityStatusUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationCustomerCapabilityStatusUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationCustomerUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationCustomerUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationMerchantCapabilityStatusUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationMerchantCapabilityStatusUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationMerchantUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationMerchantUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationRecipientCapabilityStatusUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationRecipientCapabilityStatusUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationRecipientUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationRecipientUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationStorerCapabilityStatusUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationStorerCapabilityStatusUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingConfigurationStorerUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingConfigurationStorerUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingDefaultsUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingDefaultsUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingIdentityUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingIdentityUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountIncludingRequirementsUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountIncludingRequirementsUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountLinkReturnedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountLinkReturnedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountPersonCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountPersonCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountPersonDeletedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountPersonDeletedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreAccountPersonUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreAccountPersonUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreEventDestinationPingEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreEventDestinationPingEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2CoreHealthEventGenerationFailureResolvedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2CoreHealthEventGenerationFailureResolvedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementAdjustmentCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementAdjustmentCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementFinancialAccountCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementFinancialAccountCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementFinancialAccountUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementFinancialAccountUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementFinancialAddressActivatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementFinancialAddressActivatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementFinancialAddressFailedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementFinancialAddressFailedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementInboundTransferAvailableEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementInboundTransferAvailableEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementInboundTransferBankDebitFailedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementInboundTransferBankDebitFailedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementInboundTransferBankDebitProcessingEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementInboundTransferBankDebitProcessingEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementInboundTransferBankDebitQueuedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementInboundTransferBankDebitQueuedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementInboundTransferBankDebitReturnedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementInboundTransferBankDebitReturnedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementInboundTransferBankDebitSucceededEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementInboundTransferBankDebitSucceededEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundPaymentCanceledEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundPaymentCanceledEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundPaymentCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundPaymentCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundPaymentFailedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundPaymentFailedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundPaymentPostedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundPaymentPostedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundPaymentReturnedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundPaymentReturnedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundPaymentUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundPaymentUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundTransferCanceledEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundTransferCanceledEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundTransferCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundTransferCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundTransferFailedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundTransferFailedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundTransferPostedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundTransferPostedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundTransferReturnedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundTransferReturnedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementOutboundTransferUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementOutboundTransferUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementPayoutMethodUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementPayoutMethodUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedCreditAvailableEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedCreditAvailableEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedCreditFailedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedCreditFailedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedCreditReturnedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedCreditReturnedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedCreditSucceededEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedCreditSucceededEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedDebitCanceledEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedDebitCanceledEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedDebitFailedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedDebitFailedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedDebitPendingEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedDebitPendingEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedDebitSucceededEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedDebitSucceededEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementReceivedDebitUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementReceivedDebitUpdatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementTransactionCreatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementTransactionCreatedEventNotification(&blk); + end + + sig do + params(blk: T.proc.params(event_notification: ::Stripe::Events::V2MoneyManagementTransactionUpdatedEventNotification, client: ::Stripe::StripeClient).void).void + end + def on_V2MoneyManagementTransactionUpdatedEventNotification(&blk); + end + + + # event-handler-methods: The end of the section generated from our OpenAPI spec + end diff --git a/test/stripe/stripe_event_router_test.rb b/test/stripe/stripe_event_router_test.rb new file mode 100644 index 000000000..c64e235d1 --- /dev/null +++ b/test/stripe/stripe_event_router_test.rb @@ -0,0 +1,411 @@ +# frozen_string_literal: true + +require File.expand_path("../test_helper", __dir__) +require "json" + +module Stripe + class StripeEventRouterTest < Test::Unit::TestCase + V1_BILLING_METER_PAYLOAD = { + "id" => "evt_123", + "object" => "v2.core.event", + "type" => "v1.billing.meter.error_report_triggered", + "livemode" => false, + "created" => "2022-02-15T00:27:45.330Z", + "context" => "event_context_456", + "related_object" => { + "id" => "mtr_123", + "type" => "billing.meter", + "url" => "/v1/billing/meters/mtr_123", + }, + }.to_json + + V2_ACCOUNT_CREATED_PAYLOAD = { + "id" => "evt_789", + "object" => "v2.core.event", + "type" => "v2.core.account.created", + "livemode" => false, + "created" => "2022-02-15T00:27:45.330Z", + "context" => nil, + "related_object" => { + "id" => "acct_abc", + "type" => "account", + "url" => "/v2/core/accounts/acct_abc", + }, + }.to_json + + V2_ACCOUNT_UPDATED_PAYLOAD = { + "id" => "evt_999", + "object" => "v2.core.event", + "type" => "v2.core.account.updated", + "livemode" => false, + "created" => "2022-02-15T00:27:45.330Z", + "context" => "event_context_999", + "related_object" => { + "id" => "acct_xyz", + "type" => "account", + "url" => "/v2/core/accounts/acct_xyz", + }, + }.to_json + + UNKNOWN_EVENT_PAYLOAD = { + "id" => "evt_unknown", + "object" => "v2.core.event", + "type" => "llama.created", + "livemode" => false, + "created" => "2022-02-15T00:27:45.330Z", + "context" => "event_context_unknown", + "related_object" => { + "id" => "llama_123", + "type" => "llama", + "url" => "/v1/llamas/llama_123", + }, + }.to_json + + setup do + @client = StripeClient.new("sk_test_123") + @on_unhandled_calls = [] + @on_unhandled_handler = lambda { |notif, client, details| + @on_unhandled_calls << { notif: notif, client: client, details: details } + } + end + + context "StripeEventRouter" do + should "raise ArgumentError when initialized without block" do + assert_raises(ArgumentError, "You must pass a block to respond to unhandled events") do + StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET) + end + end + + should "route event to registered handler" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + handler_called = false + received_notif = nil + received_client = nil + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |notif, client| + handler_called = true + received_notif = notif + received_client = client + end + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert handler_called + assert_not_nil received_notif + assert received_notif.is_a?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) + assert_equal "v1.billing.meter.error_report_triggered", received_notif.type + assert_equal "evt_123", received_notif.id + assert_not_nil received_client + assert_equal 0, @on_unhandled_calls.length + end + + should "route different events to correct handlers" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + billing_handler_called = false + account_handler_num_calls = 0 + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |notif, _client| + billing_handler_called = true + assert notif.is_a?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) + end + + router.on_V2CoreAccountCreatedEventNotification do |notif, _client| + account_handler_num_calls += 1 + assert notif.is_a?(Stripe::Events::V2CoreAccountCreatedEventNotification) + end + + sig_header1 = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header1) + + sig_header2 = Test::WebhookHelpers.generate_header(payload: V2_ACCOUNT_CREATED_PAYLOAD) + router.handle(V2_ACCOUNT_CREATED_PAYLOAD, sig_header2) + + sig_header2 = Test::WebhookHelpers.generate_header(payload: V2_ACCOUNT_CREATED_PAYLOAD) + router.handle(V2_ACCOUNT_CREATED_PAYLOAD, sig_header2) + + assert billing_handler_called + assert_equal 2, account_handler_num_calls + assert_equal 0, @on_unhandled_calls.length + end + + should "handler receives correct event type and data" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + received_event = nil + received_client = nil + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |notif, client| + received_event = notif + received_client = client + end + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert received_event.is_a?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) + assert_equal "v1.billing.meter.error_report_triggered", received_event.type + assert_equal "evt_123", received_event.id + assert_equal "mtr_123", received_event.related_object.id + assert received_client.is_a?(StripeClient) + end + + should "not allow registering handlers after handling events" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + # Handler body + end + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + e = assert_raises(RuntimeError) do + router.on_V2CoreAccountCreatedEventNotification do |_notif, _client| + # Handler body + end + end + assert_match(/Cannot register new event handlers after handling events/, e.message) + end + + should "not allow registering duplicate handlers" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + # First handler + end + + e = assert_raises(ArgumentError) do + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + # Duplicate handler + end + end + assert_match(/Handler already registered for event type/, e.message) + end + + should "route unknown event to on_unhandled handler" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + sig_header = Test::WebhookHelpers.generate_header(payload: UNKNOWN_EVENT_PAYLOAD) + router.handle(UNKNOWN_EVENT_PAYLOAD, sig_header) + + assert_equal 1, @on_unhandled_calls.length + + call = @on_unhandled_calls[0] + assert call[:notif].is_a?(Stripe::Events::UnknownEventNotification) + assert_equal "llama.created", call[:notif].type + assert call[:client].is_a?(StripeClient) + assert call[:details].is_a?(UnhandledNotificationDetails) + assert_equal false, call[:details].is_known_event_type + end + + should "route known unregistered event to on_unhandled handler" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert_equal 1, @on_unhandled_calls.length + + call = @on_unhandled_calls[0] + assert call[:notif].is_a?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) + assert_equal "v1.billing.meter.error_report_triggered", call[:notif].type + assert call[:client].is_a?(StripeClient) + assert call[:details].is_a?(UnhandledNotificationDetails) + assert_equal true, call[:details].is_known_event_type + end + + should "not call on_unhandled for registered events" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + # Handler body + end + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert_equal 0, @on_unhandled_calls.length + end + + should "raise error when handler is registered without block" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + e = assert_raises(ArgumentError) do + router.on_V1BillingMeterErrorReportTriggeredEventNotification + end + assert_match(/Block required to register event handler/, e.message) + end + + should "validate webhook signature" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + assert_raises(Stripe::SignatureVerificationError) do + router.handle(V1_BILLING_METER_PAYLOAD, "invalid_signature") + end + end + + should "return empty list when no event types registered" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + assert_equal [], router.registered_event_types + end + + should "return single registered event type" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + # Handler body + end + + assert_equal ["v1.billing.meter.error_report_triggered"], router.registered_event_types + end + + should "return multiple registered event types" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + # Register in non-alphabetical order + router.on_V2CoreAccountUpdatedEventNotification do |_notif, _client| + # Handler body + end + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + # Handler body + end + router.on_V2CoreAccountCreatedEventNotification do |_notif, _client| + # Handler body + end + + assert_equal ["v1.billing.meter.error_report_triggered", "v2.core.account.created", "v2.core.account.updated"], router.registered_event_types + end + + should "handler can raise errors without breaking router" do + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, _client| + raise StandardError, "Handler error!" + end + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + + e = assert_raises(StandardError) do + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + end + assert_equal "Handler error!", e.message + end + + should "on_unhandled handler can raise errors" do + error_handler = lambda { |_notif, _client, _details| + raise StandardError, "Unhandled error!" + } + + router = StripeEventRouter.new(@client, Test::WebhookHelpers::SECRET, &error_handler) + + sig_header = Test::WebhookHelpers.generate_header(payload: UNKNOWN_EVENT_PAYLOAD) + + e = assert_raises(StandardError) do + router.handle(UNKNOWN_EVENT_PAYLOAD, sig_header) + end + assert_equal "Unhandled error!", e.message + end + + should "handler uses event stripe_context" do + client_with_context = StripeClient.new("sk_test_123", stripe_context: "original_context_123") + router = StripeEventRouter.new(client_with_context, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + received_context = nil + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, client| + received_context = client.requestor.config.stripe_context + end + + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert_equal "event_context_456", received_context.to_s + end + + should "restore stripe_context after handler success" do + client_with_context = StripeClient.new("sk_test_123", stripe_context: "original_context_123") + router = StripeEventRouter.new(client_with_context, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, client| + assert_equal "event_context_456", client.requestor.config.stripe_context.to_s + end + + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + end + + should "restore stripe_context after handler error" do + client_with_context = StripeClient.new("sk_test_123", stripe_context: "original_context_123") + router = StripeEventRouter.new(client_with_context, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, client| + assert_equal "event_context_456", client.requestor.config.stripe_context.to_s + raise StandardError, "Handler error!" + end + + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + + e = assert_raises(StandardError) do + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + end + assert_equal "Handler error!", e.message + + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + end + + should "set stripe_context to nil when event has no context" do + client_with_context = StripeClient.new("sk_test_123", stripe_context: "original_context_123") + router = StripeEventRouter.new(client_with_context, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + received_context = "not_nil" + + router.on_V2CoreAccountCreatedEventNotification do |_notif, client| + received_context = client.requestor.config.stripe_context + end + + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + + sig_header = Test::WebhookHelpers.generate_header(payload: V2_ACCOUNT_CREATED_PAYLOAD) + router.handle(V2_ACCOUNT_CREATED_PAYLOAD, sig_header) + + assert_nil received_context + assert_equal "original_context_123", client_with_context.requestor.config.stripe_context.to_s + end + + should "handler client retains configuration except context" do + api_key = "sk_test_custom_key" + original_context = "original_context_xyz" + + client_with_config = StripeClient.new(api_key, stripe_context: original_context) + router = StripeEventRouter.new(client_with_config, Test::WebhookHelpers::SECRET, &@on_unhandled_handler) + + received_api_key = nil + received_context = nil + + router.on_V1BillingMeterErrorReportTriggeredEventNotification do |_notif, client| + received_api_key = client.requestor.config.api_key + received_context = client.requestor.config.stripe_context + end + + sig_header = Test::WebhookHelpers.generate_header(payload: V1_BILLING_METER_PAYLOAD) + router.handle(V1_BILLING_METER_PAYLOAD, sig_header) + + assert_equal api_key, received_api_key + assert_equal "event_context_456", received_context.to_s + assert_equal original_context, client_with_config.requestor.config.stripe_context.to_s + end + end + end +end