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 +4 -4
- data/CHANGELOG.md +11 -2
- data/README.md +37 -52
- data/app/controllers/stripe_event/webhook_controller.rb +22 -34
- data/lib/stripe_event.rb +13 -28
- data/lib/stripe_event/version.rb +1 -1
- data/spec/controllers/webhook_controller_spec.rb +78 -85
- data/spec/lib/stripe_event_spec.rb +14 -86
- data/spec/spec_helper.rb +4 -2
- data/stripe_event.gemspec +1 -1
- metadata +4 -6
- data/spec/support/fixtures/evt_invalid_id.json +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a15817b36adbede92cb27a9ceea978c9b0c54331a5dcf02b62fc92e1e0cd1ca
|
4
|
+
data.tar.gz: '09022bc7c1330fa75f41cd676c841a47637438414e462e8a09aedbd7b06add6a'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4358816b6c78747d9c3b03179895cbb62bf63cf4e7a42ac3e0108447c7e2c63f361520112bd70815c550bbc53cf3c4024538c6253e4e0249a2d09ec7f208482a
|
7
|
+
data.tar.gz: 19e6384d6cdbed7393a010f4c7b812c33ebe4f45beb01df90b5776327429f40c7a6e565497ea69b12706ef01a00e4a86d2e6f45e56f2c2dc5f42a4e3b952819f
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
###
|
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
|
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
|
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
|
-
###
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
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.
|
121
|
-
api_key = Account.find_by!(
|
122
|
-
Stripe::Event.retrieve(
|
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
|
128
|
-
def call(
|
129
|
-
api_key =
|
130
|
-
|
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
|
134
|
-
Account.find_by!(
|
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.
|
130
|
+
StripeEvent.event_filter = EventFilter.new
|
141
131
|
```
|
142
132
|
|
143
|
-
|
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.
|
149
|
-
return nil if Rails.env.production? && !
|
150
|
-
|
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.
|
156
|
-
account = Account.find_by!(
|
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
|
-
|
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
|
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
|
194
|
-
|
195
|
-
|
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
|
-
|
200
|
-
|
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',
|
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(
|
4
|
+
StripeEvent.instrument(verified_event)
|
13
5
|
head :ok
|
14
|
-
rescue
|
6
|
+
rescue Stripe::SignatureVerificationError => e
|
15
7
|
log_error(e)
|
16
|
-
head :
|
8
|
+
head :bad_request
|
17
9
|
end
|
18
10
|
|
19
11
|
private
|
20
12
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
13
|
+
def verified_event
|
14
|
+
payload = request.body.read
|
15
|
+
signature = request.headers['Stripe-Signature']
|
16
|
+
possible_secrets = secrets(payload, signature)
|
25
17
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
35
|
-
if StripeEvent.signing_secret
|
36
|
-
|
37
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
data/lib/stripe_event.rb
CHANGED
@@ -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, :
|
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(
|
16
|
-
|
17
|
-
|
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
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
data/lib/stripe_event/version.rb
CHANGED
@@ -2,131 +2,124 @@ require 'rails_helper'
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe StripeEvent::WebhookController, type: :controller do
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
9
|
+
def stub_event(identifier)
|
10
|
+
JSON.parse(File.read("spec/support/fixtures/#{identifier}.json"))
|
18
11
|
end
|
19
12
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
57
|
-
StripeEvent.subscribe('charge.succeeded') { |evt| raise Stripe::StripeError, "testing" }
|
58
|
-
stub_event('evt_charge_succeeded')
|
31
|
+
routes { StripeEvent::Engine.routes }
|
59
32
|
|
60
|
-
|
61
|
-
|
33
|
+
context "without a signing secret" do
|
34
|
+
before(:each) { StripeEvent.signing_secret = nil }
|
62
35
|
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
70
|
-
|
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 "
|
73
|
-
|
50
|
+
it "denies missing signature" do
|
51
|
+
webhook nil, charge_succeeded
|
52
|
+
expect(response.code).to eq '400'
|
53
|
+
end
|
74
54
|
|
75
|
-
|
76
|
-
|
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 "
|
80
|
-
|
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
|
-
|
83
|
-
|
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 "
|
87
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
|
87
|
+
expect(response.code).to eq '200'
|
88
|
+
expect(count).to eq 0
|
106
89
|
end
|
107
90
|
|
108
|
-
|
91
|
+
it "ensures user-generated Stripe exceptions pass through" do
|
92
|
+
StripeEvent.subscribe('charge.succeeded') { |evt| raise Stripe::StripeError, "testing" }
|
109
93
|
|
110
|
-
|
111
|
-
|
94
|
+
expect { webhook_with_signature(charge_succeeded) }.to raise_error(Stripe::StripeError, /testing/)
|
95
|
+
end
|
96
|
+
end
|
112
97
|
|
113
|
-
|
114
|
-
|
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 "
|
120
|
-
|
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 "
|
126
|
-
|
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
|
-
|
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) {
|
7
|
-
let(: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(
|
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(
|
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(
|
123
|
-
StripeEvent.instrument(
|
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(
|
134
|
-
StripeEvent.instrument(
|
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(
|
155
|
-
StripeEvent.instrument(
|
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(
|
166
|
-
StripeEvent.instrument(
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -13,13 +13,15 @@ RSpec.configure do |config|
|
|
13
13
|
end
|
14
14
|
|
15
15
|
config.before do
|
16
|
-
@
|
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.
|
23
|
+
StripeEvent.signing_secrets = @signing_secrets
|
24
|
+
StripeEvent.event_filter = @event_filter
|
23
25
|
StripeEvent.backend.notifier = @notifier
|
24
26
|
end
|
25
27
|
end
|
data/stripe_event.gemspec
CHANGED
@@ -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", [">=
|
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:
|
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-
|
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: '
|
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: '
|
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
|