mirror of
https://github.com/stripe/stripe-ruby.git
synced 2025-10-04 00:00:47 -04:00
⚠️ Add strongly typed EventNotifications (#1650)
* 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
This commit is contained in:
parent
4cde8ca569
commit
cf0db6f745
@ -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:
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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"
|
||||
|
70
lib/stripe/event_notification.rb
Normal file
70
lib/stripe/event_notification.rb
Normal file
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
41
rbi/stripe/event_notification.rbi
Normal file
41
rbi/stripe/event_notification.rbi
Normal file
@ -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
|
17
rbi/stripe/stripe_client.rbi
Normal file
17
rbi/stripe/stripe_client.rbi
Normal file
@ -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
|
@ -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"]
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user