stripe_event 1.9.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f827c24a3682b3e707e6f9547bf4237ddda2ffa07341bed8207e6090372abd0
4
- data.tar.gz: cb1aa0db4c845265921b5755a8f49aa3701447d113ed2139b77b1273eef5d3f1
3
+ metadata.gz: 5a15817b36adbede92cb27a9ceea978c9b0c54331a5dcf02b62fc92e1e0cd1ca
4
+ data.tar.gz: '09022bc7c1330fa75f41cd676c841a47637438414e462e8a09aedbd7b06add6a'
5
5
  SHA512:
6
- metadata.gz: ed4f46f5d8ce37d6dd55fd91e183b2327f0cd06c3048bff9c900124335745cb95895eb0fa6c7f409db6b46f1f275055a54ab4cf07e8451c42900535c50e4a5cd
7
- data.tar.gz: 8e20e3249afa9b7320df0abbcac2584788d2904b51030c6b21768cbe2def63a7d43d0bb445de1b5520fc63b7493ee0afa0efde13f372dc7036f48d91edba7fb7
6
+ metadata.gz: 4358816b6c78747d9c3b03179895cbb62bf63cf4e7a42ac3e0108447c7e2c63f361520112bd70815c550bbc53cf3c4024538c6253e4e0249a2d09ec7f208482a
7
+ data.tar.gz: 19e6384d6cdbed7393a010f4c7b812c33ebe4f45beb01df90b5776327429f40c7a6e565497ea69b12706ef01a00e4a86d2e6f45e56f2c2dc5f42a4e3b952819f
@@ -1,4 +1,13 @@
1
- ### 1.9.1 (November 30, 2017)
1
+ ### 2.0.0 (December 14, 2017)
2
+
3
+ **Backwards incompatible release. [Signed webhooks are now required.](https://stripe.com/docs/webhooks#signatures).**
4
+
5
+ - **Requires `StripeEvent.signing_secret` configuration** (#95, #97)
6
+ - Adds support for multiple signing secrets using `StripeEvent.signing_secrets` (#98, #99)
7
+ - Removes `StripeEvent.authentication_secret` and associated basic auth support (#97)
8
+ - Adds `StripeEvent.event_filter` (replaces use-cases for the now removed `event_retriever` config) (#97)
9
+
10
+ ### 1.9.1 (December 5, 2017)
2
11
 
3
12
  This release is in preparation for some backward incompatible changes due to
4
13
  arrive in v2.0.0. It is highly recommended that everyone secure their webhook
@@ -7,7 +16,7 @@ documentation](https://stripe.com/docs/webhooks#signatures) for more
7
16
  information.
8
17
 
9
18
  * Deprecate `StripeEvent.authentication_secret` (#96)
10
- * Deprecate unverified use of Stripe webhook's (#96)
19
+ * Deprecate unverified use of Stripe's webhooks (#96)
11
20
 
12
21
  ### 1.9.0 (November 30, 2017)
13
22
 
data/README.md CHANGED
@@ -79,86 +79,76 @@ end
79
79
 
80
80
  ## Securing your webhook endpoint
81
81
 
82
- ### Authenticating webhooks with signatures (recommended)
82
+ ### Authenticating webhooks with signatures
83
83
 
84
84
  Stripe will cryptographically sign webhook payloads with a signature that is included in a special header sent with the request. Verifying this signature lets your application properly authenticate the request originated from Stripe. To leverage this feature, please set the `signing_secret` configuration value:
85
85
 
86
- ```
86
+ ```ruby
87
87
  StripeEvent.signing_secret = Rails.application.secrets.stripe_signing_secret
88
88
  ```
89
89
 
90
90
  Please refer to Stripe's documentation for more details: https://stripe.com/docs/webhooks#signatures
91
91
 
92
- ### Basic authentication (DEPRECATED)
93
-
94
- StripeEvent automatically fetches events from Stripe to ensure they haven't been forged. However, that doesn't prevent an attacker who knows your endpoint name and an event's ID from forcing your server to process a legitimate event twice. If that event triggers some useful action, like generating a license key or enabling a delinquent account, you could end up giving something the attacker is supposed to pay for away for free.
95
-
96
- To prevent this, StripeEvent supports using HTTP Basic authentication on your webhook endpoint. If only Stripe knows the basic authentication password, this ensures that the request really comes from Stripe. Here's what you do:
92
+ ### Support for multiple signing secrets
97
93
 
98
- 1. Arrange for a secret key to be available in your application's environment variables or `secrets.yml` file. You can generate a suitable secret with the `rake secret` command. (Remember, the `secrets.yml` file shouldn't contain production secrets directly; it should use ERB to include them.)
94
+ Sometimes, you'll have multiple Stripe webhook subscriptions pointing at your application each with a different signing secret. For example, you might have both a main Account webhook and a webhook for a Connect application point at the same endpoint. It's possible to configure an array of signing secrets using the `signing_secrets` configuration option. The first one that successfully matches for each incoming webhook will be used to verify your incoming events.
99
95
 
100
- 2. Configure StripeEvent to require that secret be used as a basic authentication password, using code along the lines of these examples:
101
-
102
- ```ruby
103
- # STRIPE_WEBHOOK_SECRET environment variable
104
- StripeEvent.authentication_secret = ENV['STRIPE_WEBHOOK_SECRET']
105
- # stripe_webhook_secret key in secrets.yml file
106
- StripeEvent.authentication_secret = Rails.application.secrets.stripe_webhook_secret
107
- ```
108
-
109
- 3. When you specify your webhook's URL in Stripe's settings, include the secret as a password in the URL, along with any username:
110
-
111
- https://stripe:my-secret-key@myapplication.com/my-webhook-path
96
+ ```ruby
97
+ StripeEvent.signing_secrets = [
98
+ Rails.application.secrets.stripe_account_signing_secret,
99
+ Rails.application.secrets.stripe_connect_signing_secret,
100
+ ]
101
+ ```
112
102
 
113
- This is only truly secure if your webhook endpoint is accessed over SSL, which Stripe strongly recommends anyway.
103
+ (NOTE: `signing_secret=` and `signing_secrets=` are just aliases for one another)
114
104
 
115
105
  ## Configuration
116
106
 
117
107
  If you have built an application that has multiple Stripe accounts--say, each of your customers has their own--you may want to define your own way of retrieving events from Stripe (e.g. perhaps you want to use the [account parameter](https://stripe.com/docs/connect/webhooks) from the top level to detect the customer for the event, then grab their specific API key). You can do this:
118
108
 
119
109
  ```ruby
120
- StripeEvent.event_retriever = lambda do |params|
121
- api_key = Account.find_by!(stripe_user_id: params[:account]).api_key
122
- Stripe::Event.retrieve(params[:id], api_key)
110
+ StripeEvent.event_filter = lambda do |event|
111
+ api_key = Account.find_by!(stripe_account_id: event.account).api_key
112
+ Stripe::Event.retrieve(event.id, api_key)
123
113
  end
124
114
  ```
125
115
 
126
116
  ```ruby
127
- class EventRetriever
128
- def call(params)
129
- api_key = retrieve_api_key(params[:account])
130
- Stripe::Event.retrieve(params[:id], api_key)
117
+ class EventFilter
118
+ def call(event)
119
+ event.api_key = lookup_api_key(event.account)
120
+ event
131
121
  end
132
122
 
133
- def retrieve_api_key(stripe_user_id)
134
- Account.find_by!(stripe_user_id: stripe_user_id).api_key
123
+ def lookup_api_key(account_id)
124
+ Account.find_by!(stripe_account_id: account_id).api_key
135
125
  rescue ActiveRecord::RecordNotFound
136
126
  # whoops something went wrong - error handling
137
127
  end
138
128
  end
139
129
 
140
- StripeEvent.event_retriever = EventRetriever.new
130
+ StripeEvent.event_filter = EventFilter.new
141
131
  ```
142
132
 
143
- *Note: Older versions of Stripe used `user_id` to reference the Connect account.*
144
-
145
- If you'd like to ignore particular webhook events (perhaps to ignore test webhooks in production, or to ignore webhooks for a non-paying customer), you can do so by returning `nil` in you custom `event_retriever`. For example:
133
+ If you'd like to ignore particular webhook events (perhaps to ignore test webhooks in production, or to ignore webhooks for a non-paying customer), you can do so by returning `nil` in your custom `event_filter`. For example:
146
134
 
147
135
  ```ruby
148
- StripeEvent.event_retriever = lambda do |params|
149
- return nil if Rails.env.production? && !params[:livemode]
150
- Stripe::Event.retrieve(params[:id])
136
+ StripeEvent.event_filter = lambda do |event|
137
+ return nil if Rails.env.production? && !event.livemode
138
+ event
151
139
  end
152
140
  ```
153
141
 
154
142
  ```ruby
155
- StripeEvent.event_retriever = lambda do |params|
156
- account = Account.find_by!(stripe_user_id: params[:user_id])
143
+ StripeEvent.event_filter = lambda do |event|
144
+ account = Account.find_by!(stripe_account_id: event.account)
157
145
  return nil if account.delinquent?
158
- Stripe::Event.retrieve(params[:id], account.api_key)
146
+ event
159
147
  end
160
148
  ```
161
149
 
150
+ *Note: Older versions of Stripe used `event.user_id` to reference the Connect Account ID.*
151
+
162
152
  ## Without Rails
163
153
 
164
154
  StripeEvent can be used outside of Rails applications as well. Here is a basic Sinatra implementation:
@@ -183,25 +173,24 @@ end
183
173
 
184
174
  ## Testing
185
175
 
186
- Handling webhooks is a critical piece of modern billing systems. Verifying the behavior of StripeEvent subscribers can be done fairly easily by stubbing out the HTTP request used to authenticate the webhook request. Tools like [Webmock](https://github.com/bblimke/webmock) and [VCR](https://github.com/vcr/vcr) work well. [RequestBin](http://requestb.in/) is great for collecting the payloads. For exploratory phases of development, [UltraHook](http://www.ultrahook.com/) and other tools can forward webhook requests directly to localhost. You can check out [test-hooks](https://github.com/invisiblefunnel/test-hooks), an example Rails application to see how to test StripeEvent subscribers with RSpec request specs and Webmock. A quick look:
176
+ Handling webhooks is a critical piece of modern billing systems. Verifying the behavior of StripeEvent subscribers can be done fairly easily by stubbing out the HTTP signature header used to authenticate the webhook request. Tools like [Webmock](https://github.com/bblimke/webmock) and [VCR](https://github.com/vcr/vcr) work well. [RequestBin](http://requestb.in/) is great for collecting the payloads. For exploratory phases of development, [UltraHook](http://www.ultrahook.com/) and other tools can forward webhook requests directly to localhost. You can check out [test-hooks](https://github.com/invisiblefunnel/test-hooks), an example Rails application to see how to test StripeEvent subscribers with RSpec request specs and Webmock. A quick look:
187
177
 
188
178
  ```ruby
189
179
  # spec/requests/billing_events_spec.rb
190
180
  require 'spec_helper'
191
181
 
192
182
  describe "Billing Events" do
193
- def stub_event(fixture_id, status = 200)
194
- stub_request(:get, "https://api.stripe.com/v1/events/#{fixture_id}").
195
- to_return(status: status, body: File.read("spec/support/fixtures/#{fixture_id}.json"))
183
+ def bypass_event_signature(payload)
184
+ event = Stripe::Event.construct_from(JSON.parse(payload, symbolize_names: true))
185
+ expect(Stripe::Webhook).to receive(:construct_event).and_return(event)
196
186
  end
197
187
 
198
188
  describe "customer.created" do
199
- before do
200
- stub_event 'evt_customer_created'
201
- end
189
+ let(:payload) { File.read("spec/support/fixtures/evt_customer_created.json")
190
+ before(:each) { bypass_event_signature payload }
202
191
 
203
192
  it "is successful" do
204
- post '/_billing_events', id: 'evt_customer_created'
193
+ post '/_billing_events', body: payload
205
194
  expect(response.code).to eq "200"
206
195
  # Additional expectations...
207
196
  end
@@ -209,10 +198,6 @@ describe "Billing Events" do
209
198
  end
210
199
  ```
211
200
 
212
- ### Note: 'Test Webhooks' Button on Stripe Dashboard
213
-
214
- This button sends an example event to your webhook urls, including an `id` of `evt_00000000000000`. To confirm that Stripe sent the webhook, StripeEvent attempts to retrieve the event details from Stripe using the given `id`. In this case the event does not exist and StripeEvent responds with `401 Unauthorized`. Instead of using the 'Test Webhooks' button, trigger webhooks by using the Stripe API or Dashboard to create test payments, customers, etc.
215
-
216
201
  ### Maintainers
217
202
 
218
203
  * [Ryan McGeary](https://github.com/rmm5t)
@@ -1,52 +1,40 @@
1
1
  module StripeEvent
2
2
  class WebhookController < ActionController::Base
3
- if respond_to?(:before_action)
4
- before_action :request_authentication
5
- before_action :verify_signature
6
- else
7
- before_filter :request_authentication
8
- before_filter :verify_signature
9
- end
10
-
11
3
  def event
12
- StripeEvent.instrument(params)
4
+ StripeEvent.instrument(verified_event)
13
5
  head :ok
14
- rescue StripeEvent::UnauthorizedError => e
6
+ rescue Stripe::SignatureVerificationError => e
15
7
  log_error(e)
16
- head :unauthorized
8
+ head :bad_request
17
9
  end
18
10
 
19
11
  private
20
12
 
21
- def log_error(e)
22
- logger.error e.message
23
- e.backtrace.each { |line| logger.error " #{line}" }
24
- end
13
+ def verified_event
14
+ payload = request.body.read
15
+ signature = request.headers['Stripe-Signature']
16
+ possible_secrets = secrets(payload, signature)
25
17
 
26
- def request_authentication
27
- if StripeEvent.authentication_secret
28
- authenticate_or_request_with_http_basic do |username, password|
29
- ActiveSupport::SecurityUtils.variable_size_secure_compare password, StripeEvent.authentication_secret
18
+ possible_secrets.each_with_index do |secret, i|
19
+ begin
20
+ return Stripe::Webhook.construct_event(payload, signature, secret.to_s)
21
+ rescue Stripe::SignatureVerificationError
22
+ raise if i == possible_secrets.length - 1
23
+ next
30
24
  end
31
25
  end
32
26
  end
33
27
 
34
- def verify_signature
35
- if StripeEvent.signing_secret
36
- payload = request.body.read
37
- signature = request.headers['Stripe-Signature']
28
+ def secrets(payload, signature)
29
+ return StripeEvent.signing_secrets if StripeEvent.signing_secret
30
+ raise Stripe::SignatureVerificationError.new(
31
+ "Cannot verify signature without a `StripeEvent.signing_secret`",
32
+ signature, http_body: payload)
33
+ end
38
34
 
39
- Stripe::Webhook::Signature.verify_header payload, signature, StripeEvent.signing_secret
40
- else
41
- ActiveSupport::Deprecation.warn(
42
- "[STRIPE_EVENT] Unverified use of stripe webhooks is deprecated and configuration of " +
43
- "`StripeEvent.signing_secret=` will be required in 2.x. The value for your specific " +
44
- "endpoint's signing secret (starting with `whsec_`) is in your API > Webhooks settings " +
45
- "(https://dashboard.stripe.com/account/webhooks). " +
46
- "More information can be found here: https://stripe.com/docs/webhooks#signatures")
47
- end
48
- rescue Stripe::SignatureVerificationError
49
- head :bad_request
35
+ def log_error(e)
36
+ logger.error e.message
37
+ e.backtrace.each { |line| logger.error " #{line}" }
50
38
  end
51
39
  end
52
40
  end
@@ -4,7 +4,8 @@ require "stripe_event/engine" if defined?(Rails)
4
4
 
5
5
  module StripeEvent
6
6
  class << self
7
- attr_accessor :adapter, :backend, :event_retriever, :namespace, :authentication_secret, :signing_secret
7
+ attr_accessor :adapter, :backend, :namespace, :event_filter
8
+ attr_reader :signing_secrets
8
9
 
9
10
  def configure(&block)
10
11
  raise ArgumentError, "must provide a block" unless block_given?
@@ -12,24 +13,9 @@ module StripeEvent
12
13
  end
13
14
  alias :setup :configure
14
15
 
15
- def instrument(params)
16
- begin
17
- event = event_retriever.call(params)
18
- rescue Stripe::AuthenticationError => e
19
- if params[:type] == "account.application.deauthorized"
20
- if defined?(ActionController::Parameters) && params.is_a?(ActionController::Parameters)
21
- params = params.permit!.to_h
22
- end
23
-
24
- event = Stripe::Event.construct_from(params.deep_symbolize_keys)
25
- else
26
- raise UnauthorizedError.new(e)
27
- end
28
- rescue Stripe::StripeError => e
29
- raise UnauthorizedError.new(e)
30
- end
31
-
32
- backend.instrument namespace.call(event[:type]), event if event
16
+ def instrument(event)
17
+ event = event_filter.call(event)
18
+ backend.instrument namespace.call(event.type), event if event
33
19
  end
34
20
 
35
21
  def subscribe(name, callable = Proc.new)
@@ -45,14 +31,13 @@ module StripeEvent
45
31
  backend.notifier.listening?(namespaced_name)
46
32
  end
47
33
 
48
- def authentication_secret=(value)
49
- ActiveSupport::Deprecation.warn(
50
- "[STRIPE_EVENT] `StripeEvent.authentication_secret=` is deprecated and will be " +
51
- "removed in 2.x. Use `StripeEvent.signing_secret=` instead. The value " +
52
- "for your specific endpoint's signing secret (starting with `whsec_`) is in your " +
53
- "API > Webhooks settings (https://dashboard.stripe.com/account/webhooks). " +
54
- "More information can be found here: https://stripe.com/docs/webhooks#signatures")
55
- @authentication_secret = value
34
+ def signing_secret=(value)
35
+ @signing_secrets = Array(value)
36
+ end
37
+ alias signing_secrets= signing_secret=
38
+
39
+ def signing_secret
40
+ self.signing_secrets && self.signing_secrets.first
56
41
  end
57
42
  end
58
43
 
@@ -82,6 +67,6 @@ module StripeEvent
82
67
 
83
68
  self.adapter = NotificationAdapter
84
69
  self.backend = ActiveSupport::Notifications
85
- self.event_retriever = lambda { |params| Stripe::Event.retrieve(params[:id]) }
86
70
  self.namespace = Namespace.new("stripe_event", ".")
71
+ self.event_filter = lambda { |event| event }
87
72
  end
@@ -1,3 +1,3 @@
1
1
  module StripeEvent
2
- VERSION = "1.9.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -2,131 +2,124 @@ require 'rails_helper'
2
2
  require 'spec_helper'
3
3
 
4
4
  describe StripeEvent::WebhookController, type: :controller do
5
- def stub_event(identifier, status = 200)
6
- stub_request(:get, "https://api.stripe.com/v1/events/#{identifier}").
7
- to_return(status: status, body: File.read("spec/support/fixtures/#{identifier}.json"))
8
- end
9
-
10
- def webhook(params)
11
- data = if Rails::VERSION::MAJOR > 4
12
- { params: params }
13
- else
14
- params
15
- end
5
+ let(:secret1) { 'secret1' }
6
+ let(:secret2) { 'secret2' }
7
+ let(:charge_succeeded) { stub_event('evt_charge_succeeded') }
16
8
 
17
- post :event, data
9
+ def stub_event(identifier)
10
+ JSON.parse(File.read("spec/support/fixtures/#{identifier}.json"))
18
11
  end
19
12
 
20
- routes { StripeEvent::Engine.routes }
21
-
22
- it "succeeds with valid event data" do
23
- count = 0
24
- StripeEvent.subscribe('charge.succeeded') { |evt| count += 1 }
25
- stub_event('evt_charge_succeeded')
13
+ def generate_signature(params, secret)
14
+ payload = params.to_json
15
+ timestamp = Time.now.to_i
16
+ signature = Stripe::Webhook::Signature.send(:compute_signature, "#{timestamp}.#{payload}", secret)
26
17
 
27
- webhook id: 'evt_charge_succeeded'
28
-
29
- expect(response.code).to eq '200'
30
- expect(count).to eq 1
18
+ "t=#{timestamp},v1=#{signature}"
31
19
  end
32
20
 
33
- it "succeeds when the event_retriever returns nil (simulating an ignored webhook event)" do
34
- count = 0
35
- StripeEvent.event_retriever = lambda { |params| return nil }
36
- StripeEvent.subscribe('charge.succeeded') { |evt| count += 1 }
37
- stub_event('evt_charge_succeeded')
38
-
39
- webhook id: 'evt_charge_succeeded'
40
-
41
- expect(response.code).to eq '200'
42
- expect(count).to eq 0
21
+ def webhook(signature, params)
22
+ request.env['HTTP_STRIPE_SIGNATURE'] = signature
23
+ request.env['RAW_POST_DATA'] = params.to_json # works with Rails 3, 4, or 5
24
+ post :event
43
25
  end
44
26
 
45
- it "denies access with invalid event data" do
46
- count = 0
47
- StripeEvent.subscribe('charge.succeeded') { |evt| count += 1 }
48
- stub_event('evt_invalid_id', 404)
49
-
50
- webhook id: 'evt_invalid_id'
51
-
52
- expect(response.code).to eq '401'
53
- expect(count).to eq 0
27
+ def webhook_with_signature(params, secret = secret1)
28
+ webhook generate_signature(params, secret), params
54
29
  end
55
30
 
56
- it "ensures user-generated Stripe exceptions pass through" do
57
- StripeEvent.subscribe('charge.succeeded') { |evt| raise Stripe::StripeError, "testing" }
58
- stub_event('evt_charge_succeeded')
31
+ routes { StripeEvent::Engine.routes }
59
32
 
60
- expect { webhook id: 'evt_charge_succeeded' }.to raise_error(Stripe::StripeError, /testing/)
61
- end
33
+ context "without a signing secret" do
34
+ before(:each) { StripeEvent.signing_secret = nil }
62
35
 
63
- context "with an authentication secret" do
64
- def webhook_with_secret(secret, params)
65
- request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('user', secret)
66
- webhook params
36
+ it "denies invalid signature" do
37
+ webhook "invalid signature", charge_succeeded
38
+ expect(response.code).to eq '400'
67
39
  end
68
40
 
69
- before(:each) { StripeEvent.authentication_secret = "secret" }
70
- after(:each) { StripeEvent.authentication_secret = nil }
41
+ it "denies valid signature" do
42
+ webhook_with_signature charge_succeeded
43
+ expect(response.code).to eq '400'
44
+ end
45
+ end
46
+
47
+ context "with a signing secret" do
48
+ before(:each) { StripeEvent.signing_secret = secret1 }
71
49
 
72
- it "rejects requests with no secret" do
73
- stub_event('evt_charge_succeeded')
50
+ it "denies missing signature" do
51
+ webhook nil, charge_succeeded
52
+ expect(response.code).to eq '400'
53
+ end
74
54
 
75
- webhook id: 'evt_charge_succeeded'
76
- expect(response.code).to eq '401'
55
+ it "denies invalid signature" do
56
+ webhook "invalid signature", charge_succeeded
57
+ expect(response.code).to eq '400'
77
58
  end
78
59
 
79
- it "rejects requests with incorrect secret" do
80
- stub_event('evt_charge_succeeded')
60
+ it "denies signature from wrong secret" do
61
+ webhook_with_signature charge_succeeded, 'bogus'
62
+ expect(response.code).to eq '400'
63
+ end
81
64
 
82
- webhook_with_secret 'incorrect', id: 'evt_charge_succeeded'
83
- expect(response.code).to eq '401'
65
+ it "succeeds with valid signature from correct secret" do
66
+ webhook_with_signature charge_succeeded, secret1
67
+ expect(response.code).to eq '200'
84
68
  end
85
69
 
86
- it "accepts requests with correct secret" do
87
- stub_event('evt_charge_succeeded')
70
+ it "succeeds with valid event data" do
71
+ count = 0
72
+ StripeEvent.subscribe('charge.succeeded') { |evt| count += 1 }
73
+
74
+ webhook_with_signature charge_succeeded
88
75
 
89
- webhook_with_secret 'secret', id: 'evt_charge_succeeded'
90
76
  expect(response.code).to eq '200'
77
+ expect(count).to eq 1
91
78
  end
92
- end
93
79
 
94
- context "with a signing secret" do
95
- def webhook_with_signature(signature, params)
96
- request.env['HTTP_STRIPE_SIGNATURE'] = signature
97
- webhook params
98
- end
80
+ it "succeeds when the event_filter returns nil (simulating an ignored webhook event)" do
81
+ count = 0
82
+ StripeEvent.event_filter = lambda { |event| return nil }
83
+ StripeEvent.subscribe('charge.succeeded') { |evt| count += 1 }
99
84
 
100
- def generate_signature(secret)
101
- payload = 'id=evt_charge_succeeded'
102
- timestamp = Time.now.to_i
103
- signature = Stripe::Webhook::Signature.send(:compute_signature, "#{timestamp}.#{payload}", secret)
85
+ webhook_with_signature charge_succeeded
104
86
 
105
- "t=#{timestamp},v1=#{signature}"
87
+ expect(response.code).to eq '200'
88
+ expect(count).to eq 0
106
89
  end
107
90
 
108
- let(:shared_secret) { 'secret' }
91
+ it "ensures user-generated Stripe exceptions pass through" do
92
+ StripeEvent.subscribe('charge.succeeded') { |evt| raise Stripe::StripeError, "testing" }
109
93
 
110
- before(:each) { StripeEvent.signing_secret = shared_secret }
111
- after(:each) { StripeEvent.signing_secret = nil }
94
+ expect { webhook_with_signature(charge_succeeded) }.to raise_error(Stripe::StripeError, /testing/)
95
+ end
96
+ end
112
97
 
113
- it "rejects missing signature" do
114
- webhook id: 'evt_charge_succeeded'
98
+ context "with multiple signing secrets" do
99
+ before(:each) { StripeEvent.signing_secrets = [secret1, secret2] }
115
100
 
101
+ it "denies missing signature" do
102
+ webhook nil, charge_succeeded
116
103
  expect(response.code).to eq '400'
117
104
  end
118
105
 
119
- it "rejects invalid signature" do
120
- webhook_with_signature "invalid signature", id: 'evt_charge_succeeded'
121
-
106
+ it "denies invalid signature" do
107
+ webhook "invalid signature", charge_succeeded
122
108
  expect(response.code).to eq '400'
123
109
  end
124
110
 
125
- it "accepts valid signature" do
126
- stub_event 'evt_charge_succeeded'
111
+ it "denies signature from wrong secret" do
112
+ webhook_with_signature charge_succeeded, 'bogus'
113
+ expect(response.code).to eq '400'
114
+ end
127
115
 
128
- webhook_with_signature generate_signature(shared_secret), id: 'evt_charge_succeeded'
116
+ it "succeeds with valid signature from first secret" do
117
+ webhook_with_signature charge_succeeded, secret1
118
+ expect(response.code).to eq '200'
119
+ end
129
120
 
121
+ it "succeeds with valid signature from second secret" do
122
+ webhook_with_signature charge_succeeded, secret2
130
123
  expect(response.code).to eq '200'
131
124
  end
132
125
  end
@@ -3,8 +3,10 @@ require 'spec_helper'
3
3
  describe StripeEvent do
4
4
  let(:events) { [] }
5
5
  let(:subscriber) { ->(evt){ events << evt } }
6
- let(:charge_succeeded) { double('charge succeeded') }
7
- let(:charge_failed) { double('charge failed') }
6
+ let(:charge_succeeded) { Stripe::Event.construct_from(id: 'evt_charge_succeeded', type: 'charge.succeeded') }
7
+ let(:charge_failed) { Stripe::Event.construct_from(id: 'evt_charge_failed', type: 'charge.failed') }
8
+ let(:card_created) { Stripe::Event.construct_from(id: 'event_card_created', type: 'customer.card.created') }
9
+ let(:card_updated) { Stripe::Event.construct_from(id: 'event_card_updated', type: 'customer.card.updated') }
8
10
 
9
11
  describe ".configure" do
10
12
  it "yields itself to the block" do
@@ -27,16 +29,11 @@ describe StripeEvent do
27
29
  end
28
30
 
29
31
  describe "subscribing to a specific event type" do
30
- before do
31
- expect(charge_succeeded).to receive(:[]).with(:type).and_return('charge.succeeded')
32
- expect(Stripe::Event).to receive(:retrieve).with('evt_charge_succeeded').and_return(charge_succeeded)
33
- end
34
-
35
32
  context "with a block subscriber" do
36
33
  it "calls the subscriber with the retrieved event" do
37
34
  StripeEvent.subscribe('charge.succeeded', &subscriber)
38
35
 
39
- StripeEvent.instrument(id: 'evt_charge_succeeded', type: 'charge.succeeded')
36
+ StripeEvent.instrument(charge_succeeded)
40
37
 
41
38
  expect(events).to eq [charge_succeeded]
42
39
  end
@@ -46,81 +43,20 @@ describe StripeEvent do
46
43
  it "calls the subscriber with the retrieved event" do
47
44
  StripeEvent.subscribe('charge.succeeded', subscriber)
48
45
 
49
- StripeEvent.instrument(id: 'evt_charge_succeeded', type: 'charge.succeeded')
46
+ StripeEvent.instrument(charge_succeeded)
50
47
 
51
48
  expect(events).to eq [charge_succeeded]
52
49
  end
53
50
  end
54
51
  end
55
52
 
56
- describe "subscribing to the 'account.application.deauthorized' event type" do
57
- before do
58
- expect(Stripe::Event).to receive(:retrieve).with('evt_account_application_deauthorized').and_raise(Stripe::AuthenticationError)
59
- end
60
-
61
- context "with a subscriber params with symbolized keys" do
62
- it "calls the subscriber with the retrieved event" do
63
- StripeEvent.subscribe('account.application.deauthorized', subscriber)
64
-
65
- StripeEvent.instrument(id: 'evt_account_application_deauthorized', type: 'account.application.deauthorized')
66
-
67
- expect(events.first.type).to eq 'account.application.deauthorized'
68
- expect(events.first[:type]).to eq 'account.application.deauthorized'
69
- end
70
- end
71
-
72
- # The Stripe api expects params to be passed into their StripeObject's
73
- # with symbolized keys, but the params that we pass through from a
74
- # accont.application.deauthorized webhook are a HashWithIndifferentAccess
75
- # (keys stored as strings always.
76
- context "with a subscriber params with indifferent access (stringified keys)" do
77
- it "calls the subscriber with the retrieved event" do
78
- StripeEvent.subscribe('account.application.deauthorized', subscriber)
79
-
80
- StripeEvent.instrument({ id: 'evt_account_application_deauthorized', type: 'account.application.deauthorized' }.with_indifferent_access)
81
-
82
- expect(events.first.type).to eq 'account.application.deauthorized'
83
- expect(events.first[:type]).to eq 'account.application.deauthorized'
84
- end
85
- end
86
-
87
- # Rails 5.1 uses ActionController::Parameters which is not inherited from
88
- # ActiveSupport::HashWithIndifferentAccess anymore
89
- if Rails::VERSION::MAJOR > 3
90
- context "with a subscriber params with indifferent access (controller params)" do
91
- it "calls the subscriber with the retrieved event" do
92
- StripeEvent.subscribe('account.application.deauthorized', subscriber)
93
-
94
- StripeEvent.instrument(ActionController::Parameters.new(
95
- id: 'evt_account_application_deauthorized',
96
- type: 'account.application.deauthorized'
97
- ))
98
-
99
- expect(events.first.type).to eq 'account.application.deauthorized'
100
- expect(events.first[:type]).to eq 'account.application.deauthorized'
101
- end
102
- end
103
- end
104
- end
105
-
106
53
  describe "subscribing to a namespace of event types" do
107
- let(:card_created) { double('card created') }
108
- let(:card_updated) { double('card updated') }
109
-
110
- before do
111
- expect(card_created).to receive(:[]).with(:type).and_return('customer.card.created')
112
- expect(Stripe::Event).to receive(:retrieve).with('evt_card_created').and_return(card_created)
113
-
114
- expect(card_updated).to receive(:[]).with(:type).and_return('customer.card.updated')
115
- expect(Stripe::Event).to receive(:retrieve).with('evt_card_updated').and_return(card_updated)
116
- end
117
-
118
54
  context "with a block subscriber" do
119
55
  it "calls the subscriber with any events in the namespace" do
120
56
  StripeEvent.subscribe('customer.card', &subscriber)
121
57
 
122
- StripeEvent.instrument(id: 'evt_card_created', type: 'customer.card.created')
123
- StripeEvent.instrument(id: 'evt_card_updated', type: 'customer.card.updated')
58
+ StripeEvent.instrument(card_created)
59
+ StripeEvent.instrument(card_updated)
124
60
 
125
61
  expect(events).to eq [card_created, card_updated]
126
62
  end
@@ -130,8 +66,8 @@ describe StripeEvent do
130
66
  it "calls the subscriber with any events in the namespace" do
131
67
  StripeEvent.subscribe('customer.card.', subscriber)
132
68
 
133
- StripeEvent.instrument(id: 'evt_card_updated', type: 'customer.card.updated')
134
- StripeEvent.instrument(id: 'evt_card_created', type: 'customer.card.created')
69
+ StripeEvent.instrument(card_updated)
70
+ StripeEvent.instrument(card_created)
135
71
 
136
72
  expect(events).to eq [card_updated, card_created]
137
73
  end
@@ -139,20 +75,12 @@ describe StripeEvent do
139
75
  end
140
76
 
141
77
  describe "subscribing to all event types" do
142
- before do
143
- expect(charge_succeeded).to receive(:[]).with(:type).and_return('charge.succeeded')
144
- expect(Stripe::Event).to receive(:retrieve).with('evt_charge_succeeded').and_return(charge_succeeded)
145
-
146
- expect(charge_failed).to receive(:[]).with(:type).and_return('charge.failed')
147
- expect(Stripe::Event).to receive(:retrieve).with('evt_charge_failed').and_return(charge_failed)
148
- end
149
-
150
78
  context "with a block subscriber" do
151
79
  it "calls the subscriber with all retrieved events" do
152
80
  StripeEvent.all(&subscriber)
153
81
 
154
- StripeEvent.instrument(id: 'evt_charge_succeeded', type: 'charge.succeeded')
155
- StripeEvent.instrument(id: 'evt_charge_failed', type: 'charge.failed')
82
+ StripeEvent.instrument(charge_succeeded)
83
+ StripeEvent.instrument(charge_failed)
156
84
 
157
85
  expect(events).to eq [charge_succeeded, charge_failed]
158
86
  end
@@ -162,8 +90,8 @@ describe StripeEvent do
162
90
  it "calls the subscriber with all retrieved events" do
163
91
  StripeEvent.all(subscriber)
164
92
 
165
- StripeEvent.instrument(id: 'evt_charge_succeeded', type: 'charge.succeeded')
166
- StripeEvent.instrument(id: 'evt_charge_failed', type: 'charge.failed')
93
+ StripeEvent.instrument(charge_succeeded)
94
+ StripeEvent.instrument(charge_failed)
167
95
 
168
96
  expect(events).to eq [charge_succeeded, charge_failed]
169
97
  end
@@ -13,13 +13,15 @@ RSpec.configure do |config|
13
13
  end
14
14
 
15
15
  config.before do
16
- @event_retriever = StripeEvent.event_retriever
16
+ @signing_secrets = StripeEvent.signing_secrets
17
+ @event_filter = StripeEvent.event_filter
17
18
  @notifier = StripeEvent.backend.notifier
18
19
  StripeEvent.backend.notifier = @notifier.class.new
19
20
  end
20
21
 
21
22
  config.after do
22
- StripeEvent.event_retriever = @event_retriever
23
+ StripeEvent.signing_secrets = @signing_secrets
24
+ StripeEvent.event_filter = @event_filter
23
25
  StripeEvent.backend.notifier = @notifier
24
26
  end
25
27
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- Appraisals {spec,gemfiles}/*`.split("\n")
19
19
 
20
20
  s.add_dependency "activesupport", ">= 3.1"
21
- s.add_dependency "stripe", [">= 1.6", "< 4.0"]
21
+ s.add_dependency "stripe", [">= 2.8", "< 4.0"]
22
22
 
23
23
  s.add_development_dependency "rails", [">= 3.1"]
24
24
  s.add_development_dependency "rake", "< 11.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe_event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Whalen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-05 00:00:00.000000000 Z
11
+ date: 2017-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.6'
33
+ version: '2.8'
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
36
  version: '4.0'
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: '1.6'
43
+ version: '2.8'
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '4.0'
@@ -190,7 +190,6 @@ files:
190
190
  - spec/rails_helper.rb
191
191
  - spec/spec_helper.rb
192
192
  - spec/support/fixtures/evt_charge_succeeded.json
193
- - spec/support/fixtures/evt_invalid_id.json
194
193
  - stripe_event.gemspec
195
194
  homepage: https://github.com/integrallis/stripe_event
196
195
  licenses:
@@ -259,4 +258,3 @@ test_files:
259
258
  - spec/rails_helper.rb
260
259
  - spec/spec_helper.rb
261
260
  - spec/support/fixtures/evt_charge_succeeded.json
262
- - spec/support/fixtures/evt_invalid_id.json
@@ -1,7 +0,0 @@
1
- {
2
- "error": {
3
- "message": "No such notification: invalid-id",
4
- "param": "id",
5
- "type": "invalid_request_error"
6
- }
7
- }