Last active
April 4, 2024 18:35
-
-
Save ToTenMilan/576d92c56ceb676602157a3d29f5c1e0 to your computer and use it in GitHub Desktop.
Test stripe webhook signature outside of controller context
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Controlller | |
class Webhooks::StripeController < ApplicationController | |
skip_before_action :verify_authenticity_token | |
def index | |
stripe_service = StripeService.new( | |
request.env['HTTP_STRIPE_SIGNATURE'], | |
request.body.read | |
) | |
render json: { message: stripe_service.message }, status: stripe_service.status | |
end | |
end | |
# Service class | |
class StripeService | |
attr_reader :message, :status | |
def initialize(signature, request_body) | |
@request_body = request_body | |
@signature = signature | |
@event = nil | |
construct_event | |
end | |
private | |
def construct_event | |
endpoint_secret = ENV['STRIPE_MODE'] == 'live_mode' || Rails.env.test? ? | |
ENV['WEBHOOK_SECRET_STRIPE_LIVE_MODE'] : ##### set live webhook secret for test environment | |
ENV['WEBHOOK_SECRET_STRIPE_TEST_MODE'] | |
# verify_payload | |
begin | |
@event = Stripe::Event.construct_from(JSON.parse(@request_body, symbolize_names: true)) | |
@event = Stripe::Webhook.construct_event(@request_body, @signature, endpoint_secret) | |
rescue JSON::ParserError => e | |
@message = "⚠️ Webhook error while parsing basic request. #{e.message}" | |
@status = 400 | |
return | |
rescue Stripe::SignatureVerificationError => e | |
@message = "⚠️ Webhook signature verification failed. #{e.message}" | |
@status = 400 | |
return | |
end | |
case @event.type | |
when 'some.stripe.event' | |
# do your thing | |
else | |
puts "Unhandled event type: #{@event.type}" | |
end | |
@message = 'Success' | |
@status = 200 | |
end | |
end | |
# Service spec | |
require 'rails_helper' | |
require 'openssl' | |
require 'base64' | |
describe StripeService do | |
let!(:user) { create(:user, email: '[email protected]') } | |
context 'when a real webhook request is sent' do | |
it 'returns a success message' do | |
stripe_service = StripeService.new( | |
signature, | |
stripe_real_webhook_request | |
) | |
expect(stripe_service.message).to eq('Success') | |
expect(stripe_service.status).to eq(200) | |
# expect your objects have changed after handing event | |
end | |
end | |
context 'when an invalid signature is sent' do | |
it 'returns a failed message' do | |
stripe_service = StripeService.new( | |
'invalid_signature', | |
stripe_real_webhook_request | |
) | |
expect(stripe_service.message).to include('Webhook signature verification failed') | |
expect(stripe_service.status).to eq(400) | |
# expect your objects did not change after handing event | |
end | |
end | |
context 'when a payload is invalid' do | |
it 'returns a failed message' do | |
stripe_service = StripeService.new( | |
signature, | |
'invalid_payload' | |
) | |
expect(stripe_service.message).to include('Webhook error while parsing basic request') | |
expect(stripe_service.status).to eq(400) | |
# expect your objects did not change after handing event | |
end | |
end | |
end | |
def signature | |
timestamp = Time.new | |
stripe_signature = Stripe::Webhook::Signature.compute_signature( | |
timestamp, | |
stripe_real_webhook_request, | |
ENV['WEBHOOK_SECRET_STRIPE_LIVE_MODE'] | |
) | |
"t=#{timestamp.to_i},v1=#{stripe_signature}" | |
end | |
def stripe_real_webhook_request | |
{ | |
"id": "evt_absdeabcdefabcdefabcdef", | |
"object": "event", | |
"api_version": "2023-10-16", | |
"created": 1911556325, | |
"data": { | |
"object": { | |
# get the object shape from stripe docs | |
}, | |
"type": "some.stripe.event" | |
}.to_json | |
end | |
# controller spec | |
require 'rails_helper' | |
RSpec.describe "Webhooks::StripeController", type: :request do | |
describe "POST path/to/stripe/webhooks" do | |
let(:stripe_service_mock) { instance_double(StripeService, message: 'Success', status: 200) } | |
before do | |
expect(StripeService).to receive(:new).and_return(stripe_service_mock) | |
end | |
it "returns http success" do | |
post "/path/to/stripe/webhooks" | |
expect(response).to have_http_status(:success) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Extracted from https://gist.github.com/ToTenMilan/576d92c56ceb676602157a3d29f5c1e0