422 error when testing stripe webhook - ruby-on-rails

I keep getting a 422 error when testing stripe's webhook for customer.subscription.deleted
I placed this in my config routes
post 'stripewebhooks/receive'
here is my controller
class StripewebhooksController < ApplicationController
Stripe::api_key = ENV['STRIPE_SECRET_KEY']
require 'json'
def receive
data_json = JSON.parse request.body.read
p data_json['data']['object']['customer']
if data_json[:type] == "customer.subscription.deleted"
cancel_subscription(data_event)
end
end
def cancel_subscription(data_event)
#subscription = Subscription.find_by_stripe_customer_token(data['data']['object']['customer'])
#subscription.update_attribute(:subscription_status, "inactive")
end
end
I am unclear on what is suppose to go in the parenthesis after
def cancel_subscription
I am not sure that I am suppose to put data_event or what this means.

When you get a post data from stripe, you need to return a 200 status code from your application.
try this
def receive
data_json = JSON.parse request.body.read
p data_json['data']['object']['customer']
if data_json[:type] == "customer.subscription.deleted"
# Why did you send data_event? send the parsed data_json as parameter
cancel_subscription(data_json)
end
# Return a 200 status code
render :text => '{}', :status => :ok
end

Related

I am getting a stripe signature error even though I am parsing the request with the raw_post method

I am trying to make my stripe webhook work, using the request.raw_post method instead of the request.body.read method. I am still getting this error:
Signature error
#<Stripe::SignatureVerificationError: No signatures found matching the expected signature for payload>
Also, I am getting only "200" messages in my terminal logs when running stripe trigger checkout.session.completed in my terminal so everything is supposed to work.
This is my Webhooks Controller:
class WebhooksController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
unless has_header? "RAW_POST_DATA"
raw_post_body = body
set_header("RAW_POST_DATA", raw_post_body.read(content_length))
raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
get_header "RAW_POST_DATA"
end
def create
payload = request.raw_post.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
event = nil
begin
event = Stripe::Webhook.construct_event(
payload, sig_header, Rails.application.credentials[:stripe][:webhook]
)
rescue JSON::ParserError => e
status 400
# Invalid payload
puts "Payload error"
return
rescue Stripe::SignatureVerificationError => e
# Invalid signature
puts "Signature error"
p e
return
end
# Handle the event
case event.type
when 'checkout.session.completed'
booking = Booking.find_by(checkout_session_id: event.data.object.id)
booking.update(paid: true)
booking.save
# #booking = Booking.where(checkout_session_id: event.data.object.id)
# #booking.update(paid: true)
# #booking.save
end
render json: { state: "processed" }, status: :ok
end
end
In my webhooks logs on stripe, every event is working except checkout.session.completed where I get a 500 error.
I have been struggling with this error for a while now so I would really appreciate any help :)

How to check if HTTP request status is 200 OK in Rails

I want to do something like this post but in the actual Rails app, not the testing.
I want to see if the HTTP request was successful or not. If it's not successful (aka 404 Not Found), then I want to render a different HTML. But I can't figure out the syntax to compare.
Currently, I have:
def videos
# get current_user's wistia_project_id & authorization token
#current_user = current_user
project_id = #current_user.wistia_project_id
auth_token = "blah"
request = "https://api.wistia.com/v1/projects/#{project_id}.json?api_password=#{auth_token}"
#response = HTTP.get(request).body
puts HTTP.get(request).status
# handle errors: not 200 OK
if !HTTP.get(request).status:
render "/errors.html.erb/"
end
# get embed code for each video using the hashed_id, put in list
#video_iframe_urls = JSON.parse(#response)['medias'].map do |p|
"https://fast.wistia.com/embed/iframe/#{p["hashed_id"]}?version=v1&controlsVisibleOnLoad=true&playerColor=aae3d8"
end
end
require 'net/http'
uri = URI("https://api.wistia.com/v1/projects/#{project_id}.json?api_password=#{auth_token}")
res = Net::HTTP.get_response(uri)
# Status
puts res.code # => '200'
puts res.message # => 'OK'
puts res.class.name # => 'HTTPOK'
# Body
puts res.body if res.response_body_permitted?

NoMethodError: undefined method `sort` from Twilio voice example

I am trying to set up an example Twilio Rails project that calls a person. I am following the tutorial associated with this repo and have basically a carbon copy of the codebase. I'm getting an error that I think is from this line #validator = Twilio::Util::RequestValidator.new(##twilio_token).
Here's my twilio_controller.rb
class TwilioController < ApplicationController
# Before we allow the incoming request to connect, verify
# that it is a Twilio request
skip_before_action :verify_authenticity_token
before_action :authenticate_twilio_request, :only => [
:connect
]
##twilio_sid = ENV['TWILIO_ACCOUNT_SID']
##twilio_token = ENV['TWILIO_AUTH_TOKEN']
##twilio_number = ENV['TWILIO_NUMBER']
##api_host = ENV['TWILIO_HOST']
# Render home page
def index
render 'index'
end
def voice
response = Twilio::TwiML::Response.new do |r|
r.Say "Yay! You're on Rails!", voice: "alice"
r.Sms "Well done building your first Twilio on Rails 5 app!"
end
render :xml => response.to_xml
end
# Handle a POST from our web form and connect a call via REST API
def call
contact = Contact.new
contact.user_phone = params[:userPhone]
contact.sales_phone = params[:salesPhone]
# Validate contact
if contact.valid?
#client = Twilio::REST::Client.new ##twilio_sid, ##twilio_token
# Connect an outbound call to the number submitted
#call = #client.calls.create(
:from => ##twilio_number,
:to => contact.user_phone,
:url => "#{##api_host}/connect/#{contact.encoded_sales_phone}" # Fetch instructions from this URL when the call connects
)
# Let's respond to the ajax call with some positive reinforcement
#msg = { :message => 'Phone call incoming!', :status => 'ok' }
else
# Oops there was an error, lets return the validation errors
#msg = { :message => contact.errors.full_messages, :status => 'ok' }
end
respond_to do |format|
format.json { render :json => #msg }
end
end
# This URL contains instructions for the call that is connected with a lead
# that is using the web form.
def connect
# Our response to this request will be an XML document in the "TwiML"
# format. Our Ruby library provides a helper for generating one
# of these documents
response = Twilio::TwiML::Response.new do |r|
r.Say 'FUCK.', :voice => 'alice'
# r.Dial params[:sales_number]
end
render text: response.text
end
# Authenticate that all requests to our public-facing TwiML pages are
# coming from Twilio. Adapted from the example at
# http://twilio-ruby.readthedocs.org/en/latest/usage/validation.html
# Read more on Twilio Security at https://www.twilio.com/docs/security
private
def authenticate_twilio_request
twilio_signature = request.headers['HTTP_X_TWILIO_SIGNATURE']
# Helper from twilio-ruby to validate requests.
#validator = Twilio::Util::RequestValidator.new(##twilio_token)
# the POST variables attached to the request (eg "From", "To")
# Twilio requests only accept lowercase letters. So scrub here:
post_vars = params.reject {|k, v| k.downcase == k}
is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature)
unless is_twilio_req
render :xml => (Twilio::TwiML::Response.new {|r| r.Hangup}).text, :status => :unauthorized
false
end
end
end
Error image:
I am using ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin15] and Rails 5.1.0.
Your code is most likely failing at is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature) because upon inspection of the gem's code, it is failing at sort below
data = url + params.sort.join
This is because in Rails 5.1, ActionController::Parameters no longer inherits from Hash, so Hash methods like sort (see Hash docs) will no longer work.
You will need to convert params into hash explicitly:
def authenticate_twilio_request
twilio_signature = request.headers['HTTP_X_TWILIO_SIGNATURE']
#validator = Twilio::Util::RequestValidator.new(##twilio_token)
# convert `params` which is an `ActionController::Parameters` object into `Hash`
# you will need `permit!` to strong-params-permit EVERYTHING so that they will be included in the converted `Hash` (you don't need to specifically whitelist specific parameters for now as the params are used by the Twilio gem)
params_hash = params.permit!.to_hash
post_vars = params_hash.reject {|k, v| k.downcase == k}
is_twilio_req = #validator.validate(request.url, post_vars, twilio_signature)
unless is_twilio_req
render :xml => (Twilio::TwiML::Response.new {|r| r.Hangup}).text, :status => :unauthorized
false
end
end

net/http hanging requests causing failure - RoR

I have a bit of code that checks the response code of a list of URL's and presents them back - I am having trouble with a few of URL's that are hanging which causes the application not load at all. How can I make the request to give up after 30 seconds and check the next URL marking the skipped URL as failure.
below is my current code;
(Model/status.rb)
require "net/http"
require "uri"
class Status
def initialize(url)
#url = url
end
def get_status
response.code
end
def active?
["200","203","302"].include?(get_status) ? true : false
end
private
def lookup
URI.parse(#url)
end
def http
Net::HTTP.new(lookup.host, lookup.port)
end
def request
Net::HTTP::Get.new(lookup.request_uri)
end
def response
http.request(request)
end
end
(controllers/welcome_controller.rb)
class WelcomeController < ApplicationController
def index
#syndication = [
["http://v1.syndication.u01.example.uk/organisations?apikey=bbccdd", "U.INT 01"],
["http://v1.syndication.u02.example.uk/organisations?apikey=bbccdd", "U.INT 02"],
].collect { |url| logger.info("Boom #{url[0]}"); ["#{url[1]} (#{url[0]})", Status.new(url[0]).active?] }
end
end
Got the answer..
adding the following to my "def get_status"
def get_status
begin
response.code
rescue Exception => e
Rails.logger.info("Error #{e}")
end
end
This logged the error and the went to the next URL

How to Rescue from ActionDispatch::ParamsParser::ParseError in Rails 4

Rails 4 adds an exception ActionDispatch::ParamsParser::ParseError exception but since its in the middleware stack it appears it can't be rescued in the normal controller environment. In a json API application I want respond with a standard error format.
This gist shows a strategy for inserting middleware to intercept and respond. Following this pattern I have:
application.rb:
module Traphos
class Application < Rails::Application
....
config.middleware.insert_before ActionDispatch::ParamsParser, "JSONParseError"
end
end
And the middleware is:
class JSONParseError
def initialize(app)
#app = app
end
def call(env)
begin
#app.call(env)
rescue ActionDispatch::ParamsParser::ParseError => e
[422, {}, ['Parse Error']]
end
end
end
If I run my test without the middleware I get (spec):
Failures:
1) Photo update attributes with non-parseable json
Failure/Error: patch update_url, {:description => description}, "CONTENT_TYPE" => content_type, "HTTP_ACCEPT" => accepts, "HTTP_AUTHORIZATION" => #auth
ActionDispatch::ParamsParser::ParseError:
399: unexpected token at 'description=Test+New+Description]'
Which is exactly what I would expect (ParseError that I can't rescue_from).
Now with the only change to add in the middleware above:
2) Photo update attributes with non-parseable json
Failure/Error: response.status.should eql(422)
expected: 422
got: 200
And the log shows that the standard controller action is being executed and returning a normal response (although since it didn't receive any parameters it didn't update anything).
My questions:
How can rescue from ParseError and return a custom response. Feels like I'm on the right track but not quite there.
I can't work out why, when the exception is raised and rescued, that the controller action still proceeds.
Help much appreciated, --Kip
Turns out that further up the middleware stack, ActionDispatch::ShowExceptions can be configured with an exceptions app.
module Traphos
class Application < Rails::Application
# For the exceptions app
require "#{config.root}/lib/exceptions/public_exceptions"
config.exceptions_app = Traphos::PublicExceptions.new(Rails.public_path)
end
end
Based heavily on the Rails provided one I am now using:
module Traphos
class PublicExceptions
attr_accessor :public_path
def initialize(public_path)
#public_path = public_path
end
def call(env)
exception = env["action_dispatch.exception"]
status = code_from_exception(env["PATH_INFO"][1..-1], exception)
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
body = {:status => { :code => status, :exception => exception.class.name, :message => exception.message }}
render(status, content_type, body)
end
private
def render(status, content_type, body)
format = content_type && "to_#{content_type.to_sym}"
if format && body.respond_to?(format)
render_format(status, content_type, body.public_send(format))
else
render_html(status)
end
end
def render_format(status, content_type, body)
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [body]]
end
def render_html(status)
found = false
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
if found || File.exist?(path)
render_format(status, 'text/html', File.read(path))
else
[404, { "X-Cascade" => "pass" }, []]
end
end
def code_from_exception(status, exception)
case exception
when ActionDispatch::ParamsParser::ParseError
"422"
else
status
end
end
end
end
To use it in a test environment requires setting config variables (otherwise you get the standard exception handling in development and test). So to test I have (edited to just have the key parts):
describe Photo, :type => :api do
context 'update' do
it 'attributes with non-parseable json' do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
patch update_url, {:description => description}
response.status.should eql(422)
result = JSON.parse(response.body)
result['status']['exception'].should match(/ParseError/)
Rails.application.config.consider_all_requests_local = true
Rails.application.config.action_dispatch.show_exceptions = false
end
end
end
Which performs as I need in a public API way and is adaptable for any other exceptions I may choose to customise.
This article (also from 2013) thoughtbot covers also this topic. They put their response inside this middleware service only if you requested json
if env['HTTP_ACCEPT'] =~ /application\/json/
error_output = "There was a problem in the JSON you submitted: #{error}"
return [
400, { "Content-Type" => "application/json" },
[ { status: 400, error: error_output }.to_json ]
]
else
raise error
end

Resources