From cf0db6f745b5f071878dd67d165d9298cd3339ef Mon Sep 17 00:00:00 2001 From: David Brownman <109395161+xavdid-stripe@users.noreply.github.com> Date: Wed, 24 Sep 2025 15:55:40 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20Add=20strongly=20typed=20E?= =?UTF-8?q?ventNotifications=20(#1650)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * manual changes * move eventNotification to v2 namespace * updated rbi * add tests * Add basic rbi * generate event data types * move some things, fix tests * add missing attributes * update gemspec, examples, and rbi * fix example --- .rubocop.yml | 1 + .vscode/settings.json | 3 +- examples/README.md | 2 +- ... => event_notification_webhook_handler.rb} | 12 +- lib/stripe.rb | 2 +- lib/stripe/event_notification.rb | 70 ++++++++++ lib/stripe/event_types.rb | 26 +++- ...ling_meter_error_report_triggered_event.rb | 130 ++++++++++++++++-- .../v1_billing_meter_no_meter_found_event.rb | 99 ++++++++++++- .../v2_core_event_destination_ping_event.rb | 47 +++++-- lib/stripe/stripe_client.rb | 10 +- lib/stripe/thin_event.rb | 37 ----- lib/stripe/util.rb | 15 +- rbi/stripe/event_notification.rbi | 41 ++++++ rbi/stripe/stripe_client.rbi | 17 +++ stripe.gemspec | 23 ++-- test/stripe/v2_event_test.rb | 81 +++++++---- 17 files changed, 478 insertions(+), 138 deletions(-) rename examples/{thinevent_webhook_handler.rb => event_notification_webhook_handler.rb} (66%) create mode 100644 lib/stripe/event_notification.rb delete mode 100644 lib/stripe/thin_event.rb create mode 100644 rbi/stripe/event_notification.rbi create mode 100644 rbi/stripe/stripe_client.rbi diff --git a/.rubocop.yml b/.rubocop.yml index e4f7027c..cff1b4f4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,6 +19,7 @@ Layout/LineLength: - "lib/stripe/stripe_client.rb" - "lib/stripe/resources/**/*.rb" - "lib/stripe/services/**/*.rb" + - "lib/stripe/events/**/*.rb" - "test/**/*.rb" Lint/MissingSuper: diff --git a/.vscode/settings.json b/.vscode/settings.json index c74be88e..1553da29 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,8 @@ // Show the repo name in the top window bar. "window.title": "${rootName}${separator}${activeEditorMedium}", - "editor.formatOnSave": true, + // formatting on save is very slow in ruby + "editor.formatOnSave": false, "files.trimTrailingWhitespace": true, // Rubocop settings diff --git a/examples/README.md b/examples/README.md index e652a0bd..928f8540 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,7 +5,7 @@ From the examples folder, run: e.g. -`RUBYLIB=../lib ruby thinevent_webhook_handler.rb` +`RUBYLIB=../lib ruby event_notification_webhook_handler.rb` ## Adding a new example diff --git a/examples/thinevent_webhook_handler.rb b/examples/event_notification_webhook_handler.rb similarity index 66% rename from examples/thinevent_webhook_handler.rb rename to examples/event_notification_webhook_handler.rb index cc0594ff..8b9cc83f 100644 --- a/examples/thinevent_webhook_handler.rb +++ b/examples/event_notification_webhook_handler.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true # typed: false -# thinevent_webhook_handler.rb - receive and process thin events like the +# event_notification_webhook_handler.rb - receive and process event notification like the # v1.billing.meter.error_report_triggered event. # # In this example, we: # - create a StripeClient called client -# - use client.parse_thin_event to parse the received thin event webhook body +# - use client.parse_event_notification to parse the received event notification webhook body # - call client.v2.core.events.retrieve to retrieve the full event object # - if it is a V1BillingMeterErrorReportTriggeredEvent event type, call # event.fetchRelatedObject to retrieve the Billing Meter object associated @@ -24,12 +24,10 @@ client = Stripe::StripeClient.new(api_key) post "/webhook" do webhook_body = request.body.read sig_header = request.env["HTTP_STRIPE_SIGNATURE"] - thin_event = client.parse_thin_event(webhook_body, sig_header, webhook_secret) + event_notification = client.parse_event_notification(webhook_body, sig_header, webhook_secret) - # Fetch the event data to understand the failure - event = client.v2.core.events.retrieve(thin_event.id) - if event.instance_of? Stripe::V1BillingMeterErrorReportTriggeredEvent - meter = event.fetch_related_object + if event_notification.instance_of?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) + meter = event_notification.fetch_related_object meter_id = meter.id puts "Success!", meter_id end diff --git a/lib/stripe.rb b/lib/stripe.rb index c0cb2476..8078409c 100644 --- a/lib/stripe.rb +++ b/lib/stripe.rb @@ -51,7 +51,7 @@ require "stripe/api_resource_test_helpers" require "stripe/singleton_api_resource" require "stripe/webhook" require "stripe/stripe_configuration" -require "stripe/thin_event" +require "stripe/event_notification" # Named API resources require "stripe/resources" diff --git a/lib/stripe/event_notification.rb b/lib/stripe/event_notification.rb new file mode 100644 index 00000000..bf5731b4 --- /dev/null +++ b/lib/stripe/event_notification.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Stripe + module V2 + class EventReasonRequest + attr_reader :id, :idempotency_key + + def initialize(event_reason_request_payload = {}) + @id = event_reason_request_payload[:id] + @idempotency_key = event_reason_request_payload[:idempotency_key] + end + end + + class EventReason + attr_reader :type, :request + + def initialize(event_reason_payload = {}) + @type = event_reason_payload[:type] + @request = EventReasonRequest.new(event_reason_payload[:request]) + end + end + + class RelatedObject + attr_reader :id, :type, :url + + def initialize(related_object) + @id = related_object[:id] + @type = related_object[:type] + @url = related_object[:url] + end + end + + class EventNotification + attr_reader :id, :type, :created, :context, :livemode, :reason + + def initialize(event_payload, client) + @id = event_payload[:id] + @type = event_payload[:type] + @created = event_payload[:created] + @livemode = event_payload[:livemode] + @context = event_payload[:context] + @reason = EventReason.new(event_payload[:reason]) if event_payload[:reason] + # private unless a child declares an attr_reader + @related_object = RelatedObject.new(event_payload[:related_object]) if event_payload[:related_object] + + # internal use + @client = client + end + + # Retrieves the Event that generated this EventNotification. + def fetch_event + resp = @client.raw_request(:get, "/v2/core/events/#{id}", opts: { stripe_context: context }, + usage: ["fetch_event"]) + @client.deserialize(resp.http_body, api_mode: :v2) + end + end + + class UnknownEventNotification < EventNotification + attr_reader :related_object + + def fetch_related_object + return nil if @related_object.nil? + + resp = @client.raw_request(:get, related_object.url, opts: { stripe_context: context }, + usage: ["fetch_related_object"]) + @client.deserialize(resp.http_body, api_mode: Util.get_api_mode(related_object.url)) + end + end + end +end diff --git a/lib/stripe/event_types.rb b/lib/stripe/event_types.rb index 3c0f02c0..f1acb3d3 100644 --- a/lib/stripe/event_types.rb +++ b/lib/stripe/event_types.rb @@ -2,13 +2,27 @@ module Stripe module EventTypes - def self.thin_event_names_to_classes + def self.v2_event_types_to_classes { - # The beginning of the section generated from our OpenAPI spec - V1BillingMeterErrorReportTriggeredEvent.lookup_type => V1BillingMeterErrorReportTriggeredEvent, - V1BillingMeterNoMeterFoundEvent.lookup_type => V1BillingMeterNoMeterFoundEvent, - V2CoreEventDestinationPingEvent.lookup_type => V2CoreEventDestinationPingEvent, - # The end of the section generated from our OpenAPI spec + # v2 event types: The beginning of the section generated from our OpenAPI spec + Events::V1BillingMeterErrorReportTriggeredEvent.lookup_type => + Events::V1BillingMeterErrorReportTriggeredEvent, + Events::V1BillingMeterNoMeterFoundEvent.lookup_type => Events::V1BillingMeterNoMeterFoundEvent, + Events::V2CoreEventDestinationPingEvent.lookup_type => Events::V2CoreEventDestinationPingEvent, + # v2 event types: The end of the section generated from our OpenAPI spec + } + end + + def self.event_notification_types_to_classes + { + # event notification types: The beginning of the section generated from our OpenAPI spec + Events::V1BillingMeterErrorReportTriggeredEventNotification.lookup_type => + Events::V1BillingMeterErrorReportTriggeredEventNotification, + Events::V1BillingMeterNoMeterFoundEventNotification.lookup_type => + Events::V1BillingMeterNoMeterFoundEventNotification, + Events::V2CoreEventDestinationPingEventNotification.lookup_type => + Events::V2CoreEventDestinationPingEventNotification, + # event notification types: The end of the section generated from our OpenAPI spec } end end diff --git a/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb b/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb index c5382488..17544d43 100644 --- a/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb +++ b/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb @@ -2,22 +2,122 @@ # frozen_string_literal: true module Stripe - # Occurs when a Meter has invalid async usage events. - class V1BillingMeterErrorReportTriggeredEvent < Stripe::V2::Event - def self.lookup_type - "v1.billing.meter.error_report_triggered" - end - # There is additional data present for this event, accessible with the `data` property. - # See the Stripe API docs for more information. + module Events + # Occurs when a Meter has invalid async usage events. + class V1BillingMeterErrorReportTriggeredEvent < Stripe::V2::Event + def self.lookup_type + "v1.billing.meter.error_report_triggered" + end - # Retrieves the related object from the API. Make an API request on every call. - def fetch_related_object - _request( - method: :get, - path: related_object.url, - base_address: :api, - opts: { stripe_account: context } - ) + class V1BillingMeterErrorReportTriggeredEventData < Stripe::StripeObject + class Reason < Stripe::StripeObject + class ErrorType < Stripe::StripeObject + class SampleError < Stripe::StripeObject + class Request < Stripe::StripeObject + # The request idempotency key. + attr_reader :identifier + + def self.inner_class_types + @inner_class_types = {} + end + + def self.field_remappings + @field_remappings = {} + end + end + # The error message. + attr_reader :error_message + # The request causes the error. + attr_reader :request + + def self.inner_class_types + @inner_class_types = { request: Request } + end + + def self.field_remappings + @field_remappings = {} + end + end + # Open Enum. + attr_reader :code + # The number of errors of this type. + attr_reader :error_count + # A list of sample errors of this type. + attr_reader :sample_errors + + def self.inner_class_types + @inner_class_types = { sample_errors: SampleError } + end + + def self.field_remappings + @field_remappings = {} + end + end + # The total error count within this window. + attr_reader :error_count + # The error details. + attr_reader :error_types + + def self.inner_class_types + @inner_class_types = { error_types: ErrorType } + end + + def self.field_remappings + @field_remappings = {} + end + end + # This contains information about why meter error happens. + attr_reader :reason + # Extra field included in the event's `data` when fetched from /v2/events. + attr_reader :developer_message_summary + # The start of the window that is encapsulated by this summary. + attr_reader :validation_start + # The end of the window that is encapsulated by this summary. + attr_reader :validation_end + + def self.inner_class_types + @inner_class_types = { reason: Reason } + end + + def self.field_remappings + @field_remappings = {} + end + end + + def self.inner_class_types + @inner_class_types = { data: V1BillingMeterErrorReportTriggeredEventData } + end + attr_reader :data, :related_object + + # Retrieves the related object from the API. Makes an API request on every call. + def fetch_related_object + _request( + method: :get, + path: related_object.url, + base_address: :api, + opts: { stripe_context: context } + ) + end + end + + # Occurs when a Meter has invalid async usage events. + class V1BillingMeterErrorReportTriggeredEventNotification < Stripe::V2::EventNotification + def self.lookup_type + "v1.billing.meter.error_report_triggered" + end + + attr_reader :related_object + + # Retrieves the Meter related to this EventNotification from the Stripe API. Makes an API request on every call. + def fetch_related_object + resp = @client.raw_request( + :get, + related_object.url, + opts: { stripe_context: context }, + usage: ["fetch_related_object"] + ) + @client.deserialize(resp.http_body, api_mode: Util.get_api_mode(related_object.url)) + end end end end diff --git a/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb b/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb index ca5e3b86..b7a0f09c 100644 --- a/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb +++ b/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb @@ -2,12 +2,99 @@ # frozen_string_literal: true module Stripe - # Occurs when a Meter's id is missing or invalid in async usage events. - class V1BillingMeterNoMeterFoundEvent < Stripe::V2::Event - def self.lookup_type - "v1.billing.meter.no_meter_found" + module Events + # Occurs when a Meter's id is missing or invalid in async usage events. + class V1BillingMeterNoMeterFoundEvent < Stripe::V2::Event + def self.lookup_type + "v1.billing.meter.no_meter_found" + end + + class V1BillingMeterNoMeterFoundEventData < Stripe::StripeObject + class Reason < Stripe::StripeObject + class ErrorType < Stripe::StripeObject + class SampleError < Stripe::StripeObject + class Request < Stripe::StripeObject + # The request idempotency key. + attr_reader :identifier + + def self.inner_class_types + @inner_class_types = {} + end + + def self.field_remappings + @field_remappings = {} + end + end + # The error message. + attr_reader :error_message + # The request causes the error. + attr_reader :request + + def self.inner_class_types + @inner_class_types = { request: Request } + end + + def self.field_remappings + @field_remappings = {} + end + end + # Open Enum. + attr_reader :code + # The number of errors of this type. + attr_reader :error_count + # A list of sample errors of this type. + attr_reader :sample_errors + + def self.inner_class_types + @inner_class_types = { sample_errors: SampleError } + end + + def self.field_remappings + @field_remappings = {} + end + end + # The total error count within this window. + attr_reader :error_count + # The error details. + attr_reader :error_types + + def self.inner_class_types + @inner_class_types = { error_types: ErrorType } + end + + def self.field_remappings + @field_remappings = {} + end + end + # This contains information about why meter error happens. + attr_reader :reason + # Extra field included in the event's `data` when fetched from /v2/events. + attr_reader :developer_message_summary + # The start of the window that is encapsulated by this summary. + attr_reader :validation_start + # The end of the window that is encapsulated by this summary. + attr_reader :validation_end + + def self.inner_class_types + @inner_class_types = { reason: Reason } + end + + def self.field_remappings + @field_remappings = {} + end + end + + def self.inner_class_types + @inner_class_types = { data: V1BillingMeterNoMeterFoundEventData } + end + attr_reader :data + end + + # Occurs when a Meter's id is missing or invalid in async usage events. + class V1BillingMeterNoMeterFoundEventNotification < Stripe::V2::EventNotification + def self.lookup_type + "v1.billing.meter.no_meter_found" + end end - # There is additional data present for this event, accessible with the `data` property. - # See the Stripe API docs for more information. end end diff --git a/lib/stripe/events/v2_core_event_destination_ping_event.rb b/lib/stripe/events/v2_core_event_destination_ping_event.rb index 84068c18..3bf0ce37 100644 --- a/lib/stripe/events/v2_core_event_destination_ping_event.rb +++ b/lib/stripe/events/v2_core_event_destination_ping_event.rb @@ -2,20 +2,43 @@ # frozen_string_literal: true module Stripe - # A ping event used to test the connection to an EventDestination. - class V2CoreEventDestinationPingEvent < Stripe::V2::Event - def self.lookup_type - "v2.core.event_destination.ping" + module Events + # A ping event used to test the connection to an EventDestination. + class V2CoreEventDestinationPingEvent < Stripe::V2::Event + def self.lookup_type + "v2.core.event_destination.ping" + end + + # Retrieves the related object from the API. Makes an API request on every call. + def fetch_related_object + _request( + method: :get, + path: related_object.url, + base_address: :api, + opts: { stripe_context: context } + ) + end + attr_reader :related_object end - # Retrieves the related object from the API. Make an API request on every call. - def fetch_related_object - _request( - method: :get, - path: related_object.url, - base_address: :api, - opts: { stripe_account: context } - ) + # A ping event used to test the connection to an EventDestination. + class V2CoreEventDestinationPingEventNotification < Stripe::V2::EventNotification + def self.lookup_type + "v2.core.event_destination.ping" + end + + attr_reader :related_object + + # Retrieves the EventDestination related to this EventNotification from the Stripe API. Makes an API request on every call. + def fetch_related_object + resp = @client.raw_request( + :get, + related_object.url, + opts: { stripe_context: context }, + usage: ["fetch_related_object"] + ) + @client.deserialize(resp.http_body, api_mode: Util.get_api_mode(related_object.url)) + end end end end diff --git a/lib/stripe/stripe_client.rb b/lib/stripe/stripe_client.rb index 5aa54bce..1f4b29c6 100644 --- a/lib/stripe/stripe_client.rb +++ b/lib/stripe/stripe_client.rb @@ -59,7 +59,7 @@ module Stripe extend Gem::Deprecate deprecate :request, :raw_request, 2024, 9 - def parse_thin_event(payload, sig_header, secret, tolerance: Webhook::DEFAULT_TOLERANCE) + def parse_event_notification(payload, sig_header, secret, tolerance: Webhook::DEFAULT_TOLERANCE) payload = payload.force_encoding("UTF-8") if payload.respond_to?(:force_encoding) # v2 events use the same signing mechanism as v1 events @@ -67,15 +67,17 @@ module Stripe parsed = JSON.parse(payload, symbolize_names: true) - Stripe::ThinEvent.new(parsed) + cls = Util.event_notification_classes.fetch(parsed[:type], Stripe::V2::UnknownEventNotification) + + cls.new(parsed, self) end - def raw_request(method, url, base_address: :api, params: {}, opts: {}) + def raw_request(method, url, base_address: :api, params: {}, opts: {}, usage: nil) opts = Util.normalize_opts(opts) req_opts = RequestOptions.extract_opts_from_hash(opts) params = params.to_h if params.is_a?(Stripe::RequestParams) - resp, = @requestor.send(:execute_request_internal, method, url, base_address, params, req_opts, usage: ["raw_request"]) + resp, = @requestor.send(:execute_request_internal, method, url, base_address, params, req_opts, usage: usage || ["raw_request"]) @requestor.interpret_response(resp) end diff --git a/lib/stripe/thin_event.rb b/lib/stripe/thin_event.rb deleted file mode 100644 index 9e297866..00000000 --- a/lib/stripe/thin_event.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module Stripe - class EventReasonRequest - attr_reader :id, :idempotency_key - - def initialize(event_reason_request_payload = {}) - @id = event_reason_request_payload[:id] - @idempotency_key = event_reason_request_payload[:idempotency_key] - end - end - - class EventReason - attr_reader :type, :request - - def initialize(event_reason_payload = {}) - @type = event_reason_payload[:type] - @request = EventReasonRequest.new(event_reason_payload[:request]) - end - end - - class ThinEvent - attr_reader :id, :type, :created, :context, :related_object, :livemode, :reason - - def initialize(event_payload = {}) - @id = event_payload[:id] - @type = event_payload[:type] - @created = event_payload[:created] - @context = event_payload[:context] - @livemode = event_payload[:livemode] - @related_object = event_payload[:related_object] - return if event_payload[:reason].nil? - - @reason = EventReason.new(event_payload[:reason]) - end - end -end diff --git a/lib/stripe/util.rb b/lib/stripe/util.rb index e09bdbb4..f9767e93 100644 --- a/lib/stripe/util.rb +++ b/lib/stripe/util.rb @@ -30,8 +30,12 @@ module Stripe @v2_object_classes ||= Stripe::ObjectTypes.v2_object_names_to_classes end - def self.thin_event_classes - @thin_event_classes ||= Stripe::EventTypes.thin_event_names_to_classes + def self.v2_event_classes + @v2_event_classes ||= Stripe::EventTypes.v2_event_types_to_classes + end + + def self.event_notification_classes + @event_notification_classes ||= Stripe::EventTypes.event_notification_types_to_classes end def self.object_name_matches_class?(object_name, klass) @@ -137,8 +141,7 @@ module Stripe data.map { |i| convert_to_stripe_object(i, opts, api_mode: api_mode, requestor: requestor, klass: klass) } when Hash # TODO: This is a terrible hack. - # Waiting on https://jira.corp.stripe.com/browse/API_SERVICES-3167 to add - # an object in v2 lists + # Waiting on https://go/j/API_SERVICES-3167 to add an object in v2 lists if api_mode == :v2 && data.include?(:data) && data.include?(:next_page_url) return V2::ListObject.construct_from(data, opts, last_response, api_mode, requestor) end @@ -152,8 +155,8 @@ module Stripe elsif api_mode == :v2 if v2_deleted_object V2::DeletedObject - elsif object_name == "v2.core.event" && thin_event_classes.key?(object_type) - thin_event_classes.fetch(object_type) + elsif object_name == "v2.core.event" && v2_event_classes.key?(object_type) + v2_event_classes.fetch(object_type) else v2_object_classes.fetch( object_name, StripeObject diff --git a/rbi/stripe/event_notification.rbi b/rbi/stripe/event_notification.rbi new file mode 100644 index 00000000..5b8131a5 --- /dev/null +++ b/rbi/stripe/event_notification.rbi @@ -0,0 +1,41 @@ +# frozen_string_literal: true +# typed: true + +module Stripe + module V2 + class EventReasonRequest + sig { returns(String) } + def id; end + sig { returns(String) } + def idempotency_key; end + + sig { params(event_reason_request_payload: T::Hash[T.untyped, T.untyped]).void } + def initialize(event_reason_request_payload = {}); end + end + + class EventReason + sig { returns(String) } + def type; end + sig { returns(::Stripe::V2::EventReasonRequest) } + def request; end + + sig { params(event_reason_payload: T::Hash[T.untyped, T.untyped]).void } + def initialize(event_reason_payload = {}); end + end + + class EventNotification + sig { returns(String) } + def id; end + sig { returns(String) } + def type; end + sig { returns(String) } + def created; end + sig { returns(T.nilable(String)) } + def context; end + sig { returns(T::Boolean) } + def livemode; end + sig { returns(T.nilable(::Stripe::V2::EventReason)) } + def reason; end + end + end +end diff --git a/rbi/stripe/stripe_client.rbi b/rbi/stripe/stripe_client.rbi new file mode 100644 index 00000000..6162a374 --- /dev/null +++ b/rbi/stripe/stripe_client.rbi @@ -0,0 +1,17 @@ +# frozen_string_literal: true +# typed: true + +module Stripe + class StripeClient + sig do + params( + payload: String, + sig_header: String, + secret: String, + tolerance: T.nilable(Integer) + ) + .returns(::Stripe::V2::EventNotification) + end + def parse_event_notification(payload, sig_header, secret, tolerance:); end + end +end diff --git a/stripe.gemspec b/stripe.gemspec index 0f728577..62437505 100644 --- a/stripe.gemspec +++ b/stripe.gemspec @@ -27,20 +27,17 @@ Gem::Specification.new do |s| "rubygems_mfa_required" => "false", } - ignored = Regexp.union( - /\A\.editorconfig/, - /\A\.git/, - /\A\.rubocop/, - /\A\.travis.yml/, - /\A\.vscode/, - /\Abin/, - /\Asorbet/, - /\Atest/, - # Ignores the contents of rbi/stripe/** but keeps rbi/stripe.rbi - # Only rbi/stripe.rbi is included in the gem - %r{\Arbi/stripe/} + included = Regexp.union( + %r{\Alib/}, + %r{\Aexe/}, + # generated RBI files + %r{\Arbi/stripe\.rbi\z}, + # Handwritten RBIs + # TODO(helenye): http://go/j/DEVSDK-2769 + %r{\Arbi/stripe/stripe_client.rbi\z}, + %r{\Arbi/stripe/event_notification.rbi\z} ) - s.files = `git ls-files`.split("\n").grep_v(ignored) + s.files = `git ls-files`.split("\n").grep(included) s.bindir = "exe" s.executables = `git ls-files -- exe/*`.split("\n").map { |f| File.basename(f) } s.require_paths = ["lib"] diff --git a/test/stripe/v2_event_test.rb b/test/stripe/v2_event_test.rb index 130e659f..31213068 100644 --- a/test/stripe/v2_event_test.rb +++ b/test/stripe/v2_event_test.rb @@ -6,11 +6,7 @@ require "json" module Stripe class V2EventTest < Test::Unit::TestCase def parse_signed_event(payload, secret = Test::WebhookHelpers::SECRET) - @client.parse_thin_event(payload, Test::WebhookHelpers.generate_header(payload: payload), secret) - end - - def retrieve_event(evt_id) - @client.v2.core.events.retrieve(evt_id) + @client.parse_event_notification(payload, Test::WebhookHelpers.generate_header(payload: payload), secret) end context "V2 Events" do @@ -24,6 +20,13 @@ module Stripe "created" => "2022-02-15T00:27:45.330Z", }.to_json + @v2_payload_fake_event = { + "id" => "evt_234", + "object" => "v2.core.event", + "type" => "whatever", + "created" => "2022-02-15T00:27:45.330Z", + }.to_json + @v2_push_payload = { "id" => "evt_234", "object" => "v2.core.event", @@ -92,7 +95,7 @@ module Stripe context ".event_signing" do should "parse v2 events" do event = parse_signed_event(@v2_push_payload) - assert event.is_a?(Stripe::ThinEvent) + assert event.is_a?(Stripe::V2::EventNotification) assert_equal "evt_234", event.id assert_equal "v1.billing.meter.error_report_triggered", event.type assert_equal "2022-02-15T00:27:45.330Z", event.created @@ -101,7 +104,10 @@ module Stripe should "parse v2 events with livemode and reason" do event = parse_signed_event(@v2_push_payload_with_livemode_and_reason) - assert event.is_a?(Stripe::ThinEvent) + assert event.is_a?(Stripe::V2::EventNotification) + assert event.related_object.is_a?(Stripe::V2::RelatedObject) + assert event.reason.is_a?(Stripe::V2::EventReason) + assert_equal "evt_234", event.id assert_equal "v1.billing.meter.error_report_triggered", event.type assert_equal "2022-02-15T00:27:45.330Z", event.created @@ -119,35 +125,52 @@ module Stripe end end - should "retrieve event data" do - event = parse_signed_event(@v2_push_payload) - assert event.is_a?(Stripe::ThinEvent) + context "Event notifications" do + should "parse event notifications and pull data" do + event_notif = parse_signed_event(@v2_push_payload) + assert event_notif.instance_of?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) - stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v2/core/events/evt_234") - .to_return(body: @v2_pull_payload) - ret_event = retrieve_event(event.id) - assert ret_event.is_a?(Stripe::V1BillingMeterErrorReportTriggeredEvent) + stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v1/billing/meters/mtr_123") + .to_return(body: JSON.generate({ "id" => "mtr_123", "object" => "billing.meter" })) + stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v2/core/events/evt_234") + .to_return(body: @v2_pull_payload) - assert ret_event.data.error == "bufo" - assert ret_event.data.reason.error_types[0].code == "meter_event_invalid_value" - end + meter = event_notif.fetch_related_object + assert meter.instance_of?(Stripe::Billing::Meter) + assert meter.id == "mtr_123" - should "fetch object" do - stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v1/billing/meters/mtr_123") - .to_return(body: JSON.generate({ "id" => "mtr_123", "object" => "billing.meter" })) - stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v2/core/events/evt_234") - .to_return(body: @v2_pull_payload) + event = event_notif.fetch_event + assert event.is_a?(Stripe::Events::V1BillingMeterErrorReportTriggeredEvent) + assert_equal "a", event.data.reason.error_types.first.sample_errors.first.request.identifier + end - event = parse_signed_event(@v2_push_payload) - assert event.is_a?(Stripe::ThinEvent) + should "correctly retrieve events" do + event_notif = parse_signed_event(@v2_push_payload) + stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v2/core/events/evt_234") + .to_return(body: @v2_pull_payload) - ret_event = retrieve_event(event.id) - assert ret_event.is_a?(Stripe::V1BillingMeterErrorReportTriggeredEvent) + event = @client.v2.core.events.retrieve(event_notif.id) - mtr = @client.v1.billing.meters.retrieve("mtr_123") + assert event.is_a?(Stripe::Events::V1BillingMeterErrorReportTriggeredEvent) + assert event.data.error == "bufo" + assert event.data.reason.error_types[0].code == "meter_event_invalid_value" + end - assert mtr.is_a?(Stripe::Billing::Meter) - assert mtr.id == "mtr_123" + should "parse unknown events" do + event_notif = parse_signed_event(@v2_payload_fake_event) + assert event_notif.instance_of?(Stripe::V2::UnknownEventNotification) + + stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v2/core/events/evt_234") + .to_return(body: { + "id" => "evt_234", + "object" => "v2.core.event", + "type" => "whatever", + "created" => "2022-02-15T00:27:45.330Z", + }.to_json) + + event = event_notif.fetch_event + assert event.instance_of?(Stripe::V2::Event) + end end end end