Skip to content

Commit cf0db6f

Browse files
⚠️ Add strongly typed EventNotifications (stripe#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
1 parent 4cde8ca commit cf0db6f

17 files changed

Lines changed: 478 additions & 138 deletions

.rubocop.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Layout/LineLength:
1919
- "lib/stripe/stripe_client.rb"
2020
- "lib/stripe/resources/**/*.rb"
2121
- "lib/stripe/services/**/*.rb"
22+
- "lib/stripe/events/**/*.rb"
2223
- "test/**/*.rb"
2324

2425
Lint/MissingSuper:

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Show the repo name in the top window bar.
33
"window.title": "${rootName}${separator}${activeEditorMedium}",
44

5-
"editor.formatOnSave": true,
5+
// formatting on save is very slow in ruby
6+
"editor.formatOnSave": false,
67
"files.trimTrailingWhitespace": true,
78

89
// Rubocop settings

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ From the examples folder, run:
55

66
e.g.
77

8-
`RUBYLIB=../lib ruby thinevent_webhook_handler.rb`
8+
`RUBYLIB=../lib ruby event_notification_webhook_handler.rb`
99

1010
## Adding a new example
1111

examples/thinevent_webhook_handler.rb renamed to examples/event_notification_webhook_handler.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# frozen_string_literal: true
22
# typed: false
33

4-
# thinevent_webhook_handler.rb - receive and process thin events like the
4+
# event_notification_webhook_handler.rb - receive and process event notification like the
55
# v1.billing.meter.error_report_triggered event.
66
#
77
# In this example, we:
88
# - create a StripeClient called client
9-
# - use client.parse_thin_event to parse the received thin event webhook body
9+
# - use client.parse_event_notification to parse the received event notification webhook body
1010
# - call client.v2.core.events.retrieve to retrieve the full event object
1111
# - if it is a V1BillingMeterErrorReportTriggeredEvent event type, call
1212
# event.fetchRelatedObject to retrieve the Billing Meter object associated
@@ -24,12 +24,10 @@
2424
post "/webhook" do
2525
webhook_body = request.body.read
2626
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]
27-
thin_event = client.parse_thin_event(webhook_body, sig_header, webhook_secret)
27+
event_notification = client.parse_event_notification(webhook_body, sig_header, webhook_secret)
2828

29-
# Fetch the event data to understand the failure
30-
event = client.v2.core.events.retrieve(thin_event.id)
31-
if event.instance_of? Stripe::V1BillingMeterErrorReportTriggeredEvent
32-
meter = event.fetch_related_object
29+
if event_notification.instance_of?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification)
30+
meter = event_notification.fetch_related_object
3331
meter_id = meter.id
3432
puts "Success!", meter_id
3533
end

lib/stripe.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
require "stripe/singleton_api_resource"
5252
require "stripe/webhook"
5353
require "stripe/stripe_configuration"
54-
require "stripe/thin_event"
54+
require "stripe/event_notification"
5555

5656
# Named API resources
5757
require "stripe/resources"

lib/stripe/event_notification.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# frozen_string_literal: true
2+
3+
module Stripe
4+
module V2
5+
class EventReasonRequest
6+
attr_reader :id, :idempotency_key
7+
8+
def initialize(event_reason_request_payload = {})
9+
@id = event_reason_request_payload[:id]
10+
@idempotency_key = event_reason_request_payload[:idempotency_key]
11+
end
12+
end
13+
14+
class EventReason
15+
attr_reader :type, :request
16+
17+
def initialize(event_reason_payload = {})
18+
@type = event_reason_payload[:type]
19+
@request = EventReasonRequest.new(event_reason_payload[:request])
20+
end
21+
end
22+
23+
class RelatedObject
24+
attr_reader :id, :type, :url
25+
26+
def initialize(related_object)
27+
@id = related_object[:id]
28+
@type = related_object[:type]
29+
@url = related_object[:url]
30+
end
31+
end
32+
33+
class EventNotification
34+
attr_reader :id, :type, :created, :context, :livemode, :reason
35+
36+
def initialize(event_payload, client)
37+
@id = event_payload[:id]
38+
@type = event_payload[:type]
39+
@created = event_payload[:created]
40+
@livemode = event_payload[:livemode]
41+
@context = event_payload[:context]
42+
@reason = EventReason.new(event_payload[:reason]) if event_payload[:reason]
43+
# private unless a child declares an attr_reader
44+
@related_object = RelatedObject.new(event_payload[:related_object]) if event_payload[:related_object]
45+
46+
# internal use
47+
@client = client
48+
end
49+
50+
# Retrieves the Event that generated this EventNotification.
51+
def fetch_event
52+
resp = @client.raw_request(:get, "/v2/core/events/#{id}", opts: { stripe_context: context },
53+
usage: ["fetch_event"])
54+
@client.deserialize(resp.http_body, api_mode: :v2)
55+
end
56+
end
57+
58+
class UnknownEventNotification < EventNotification
59+
attr_reader :related_object
60+
61+
def fetch_related_object
62+
return nil if @related_object.nil?
63+
64+
resp = @client.raw_request(:get, related_object.url, opts: { stripe_context: context },
65+
usage: ["fetch_related_object"])
66+
@client.deserialize(resp.http_body, api_mode: Util.get_api_mode(related_object.url))
67+
end
68+
end
69+
end
70+
end

lib/stripe/event_types.rb

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,27 @@
22

33
module Stripe
44
module EventTypes
5-
def self.thin_event_names_to_classes
5+
def self.v2_event_types_to_classes
66
{
7-
# The beginning of the section generated from our OpenAPI spec
8-
V1BillingMeterErrorReportTriggeredEvent.lookup_type => V1BillingMeterErrorReportTriggeredEvent,
9-
V1BillingMeterNoMeterFoundEvent.lookup_type => V1BillingMeterNoMeterFoundEvent,
10-
V2CoreEventDestinationPingEvent.lookup_type => V2CoreEventDestinationPingEvent,
11-
# The end of the section generated from our OpenAPI spec
7+
# v2 event types: The beginning of the section generated from our OpenAPI spec
8+
Events::V1BillingMeterErrorReportTriggeredEvent.lookup_type =>
9+
Events::V1BillingMeterErrorReportTriggeredEvent,
10+
Events::V1BillingMeterNoMeterFoundEvent.lookup_type => Events::V1BillingMeterNoMeterFoundEvent,
11+
Events::V2CoreEventDestinationPingEvent.lookup_type => Events::V2CoreEventDestinationPingEvent,
12+
# v2 event types: The end of the section generated from our OpenAPI spec
13+
}
14+
end
15+
16+
def self.event_notification_types_to_classes
17+
{
18+
# event notification types: The beginning of the section generated from our OpenAPI spec
19+
Events::V1BillingMeterErrorReportTriggeredEventNotification.lookup_type =>
20+
Events::V1BillingMeterErrorReportTriggeredEventNotification,
21+
Events::V1BillingMeterNoMeterFoundEventNotification.lookup_type =>
22+
Events::V1BillingMeterNoMeterFoundEventNotification,
23+
Events::V2CoreEventDestinationPingEventNotification.lookup_type =>
24+
Events::V2CoreEventDestinationPingEventNotification,
25+
# event notification types: The end of the section generated from our OpenAPI spec
1226
}
1327
end
1428
end

lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,122 @@
22
# frozen_string_literal: true
33

44
module Stripe
5-
# Occurs when a Meter has invalid async usage events.
6-
class V1BillingMeterErrorReportTriggeredEvent < Stripe::V2::Event
7-
def self.lookup_type
8-
"v1.billing.meter.error_report_triggered"
5+
module Events
6+
# Occurs when a Meter has invalid async usage events.
7+
class V1BillingMeterErrorReportTriggeredEvent < Stripe::V2::Event
8+
def self.lookup_type
9+
"v1.billing.meter.error_report_triggered"
10+
end
11+
12+
class V1BillingMeterErrorReportTriggeredEventData < Stripe::StripeObject
13+
class Reason < Stripe::StripeObject
14+
class ErrorType < Stripe::StripeObject
15+
class SampleError < Stripe::StripeObject
16+
class Request < Stripe::StripeObject
17+
# The request idempotency key.
18+
attr_reader :identifier
19+
20+
def self.inner_class_types
21+
@inner_class_types = {}
22+
end
23+
24+
def self.field_remappings
25+
@field_remappings = {}
26+
end
27+
end
28+
# The error message.
29+
attr_reader :error_message
30+
# The request causes the error.
31+
attr_reader :request
32+
33+
def self.inner_class_types
34+
@inner_class_types = { request: Request }
35+
end
36+
37+
def self.field_remappings
38+
@field_remappings = {}
39+
end
40+
end
41+
# Open Enum.
42+
attr_reader :code
43+
# The number of errors of this type.
44+
attr_reader :error_count
45+
# A list of sample errors of this type.
46+
attr_reader :sample_errors
47+
48+
def self.inner_class_types
49+
@inner_class_types = { sample_errors: SampleError }
50+
end
51+
52+
def self.field_remappings
53+
@field_remappings = {}
54+
end
55+
end
56+
# The total error count within this window.
57+
attr_reader :error_count
58+
# The error details.
59+
attr_reader :error_types
60+
61+
def self.inner_class_types
62+
@inner_class_types = { error_types: ErrorType }
63+
end
64+
65+
def self.field_remappings
66+
@field_remappings = {}
67+
end
68+
end
69+
# This contains information about why meter error happens.
70+
attr_reader :reason
71+
# Extra field included in the event's `data` when fetched from /v2/events.
72+
attr_reader :developer_message_summary
73+
# The start of the window that is encapsulated by this summary.
74+
attr_reader :validation_start
75+
# The end of the window that is encapsulated by this summary.
76+
attr_reader :validation_end
77+
78+
def self.inner_class_types
79+
@inner_class_types = { reason: Reason }
80+
end
81+
82+
def self.field_remappings
83+
@field_remappings = {}
84+
end
85+
end
86+
87+
def self.inner_class_types
88+
@inner_class_types = { data: V1BillingMeterErrorReportTriggeredEventData }
89+
end
90+
attr_reader :data, :related_object
91+
92+
# Retrieves the related object from the API. Makes an API request on every call.
93+
def fetch_related_object
94+
_request(
95+
method: :get,
96+
path: related_object.url,
97+
base_address: :api,
98+
opts: { stripe_context: context }
99+
)
100+
end
9101
end
10-
# There is additional data present for this event, accessible with the `data` property.
11-
# See the Stripe API docs for more information.
12-
13-
# Retrieves the related object from the API. Make an API request on every call.
14-
def fetch_related_object
15-
_request(
16-
method: :get,
17-
path: related_object.url,
18-
base_address: :api,
19-
opts: { stripe_account: context }
20-
)
102+
103+
# Occurs when a Meter has invalid async usage events.
104+
class V1BillingMeterErrorReportTriggeredEventNotification < Stripe::V2::EventNotification
105+
def self.lookup_type
106+
"v1.billing.meter.error_report_triggered"
107+
end
108+
109+
attr_reader :related_object
110+
111+
# Retrieves the Meter related to this EventNotification from the Stripe API. Makes an API request on every call.
112+
def fetch_related_object
113+
resp = @client.raw_request(
114+
:get,
115+
related_object.url,
116+
opts: { stripe_context: context },
117+
usage: ["fetch_related_object"]
118+
)
119+
@client.deserialize(resp.http_body, api_mode: Util.get_api_mode(related_object.url))
120+
end
21121
end
22122
end
23123
end

0 commit comments

Comments
 (0)