500 error for stripe webhook event checkout.session.completed - ruby-on-rails

I am currently working on stripe webhooks for my rails application and am encountering a problem. All events except for checkout.session.completed are working.
My main goal is to change the payment status booking.paid to true when the event checkout.session.completed happens. The stripe webhooks logs give me a 500 internal server error for the event checkout.session.completed. I think the problem is in my Webhook controller but I just can't figure out what's wrong. Any help would be amazing!
This is my Webhooks_controller:
class WebhooksController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
def create
payload = request.body.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
return
rescue Stripe::SignatureVerificationError => e
# Invalid signature
puts "Signature error"
p e
return
end
# Handle the event
case event.type
when 'checkout.session.completed'
# session = event.data.object
# #booking.session.client_reference_id.paid = true
booking = Booking.find_by(checkout_session_id: event.data.object.id)
booking.update(paid: true)
end
render json: { message: 'success' }
end
end

I just happen to be writing the exact same feature as you so I'm glad this popped up in my queue.
From taking a quick look at the code nothing stands out much. If we know that the only event that doesn't work is checkout.session.completed, and that's the only one we're even processing, that narrows the problem down a bit... so here's what I did:
I copied your implementation into a controller in my Rails API project, then used the Stripe CLI to listen for, and forward Stripe events to the new endpoint:
$ stripe listen --forward-to http://localhost:3000/webhook_events
I commented out the actual handling of the event so it was only processing the event.
I then used the Stripe CLI in a new terminal to trigger a checkout.session.completed event:
$ stripe trigger checkout.session.completed
Once I did this, my API responded with a 201 and Stripe was happy.
So after all of that, as the previous answer suggests, I think the issue lies with your updating the Booking model, so I have a few suggestions to make working with webhooks in general easier:
Ideally, your controller should respond with a 2xx to Stripe as soon as you've verified the authenticity of the event with the Stripe gem.
Once you've completed that, I would immediately move the processing of the event to a background job using ActiveJob.
In the background job, you know that your event is valid and that the session completed successfully, so now you can start to update your Booking model. The arguments to the job could be as simple as just the Stripe checkout session ID.
Finally, splitting the responsibilities like this will make writing tests much easier (and will catch what the actual problem is!).
I hope this helps, good luck!

I think the issue might lie in the Booking.find_by method. Try adding a line to inspect the value of booking prior to updating its status.

when 'checkout.session.completed'
under this use print(session)
it will show error in the console that is affecting or showing 500 error in checkout session

Related

Rails - multiple theads to avoid the slack 3 second API response rule

I am working with the slack API. My script does a bunch of external processing and in some cases it can take around 3-6 seconds. What is happening is the Slack API expects a 200 response within 3 seconds and because my function is not finished within 3 seconds, it retries again and then it ends up posting the same automated responses 2-3 times.
I confirmed this by commenting out all the functions and I had no issue, it posted the responses to slack fine. I then added sleep 10 and it done the same responses 3 times so the ohly thing different was it took longer.
From what I read, I need to have threaded responses. I then need to first respond to the slack API in thread 1 and then go about processing my functions.
Here is what I tried:
def events
Thread.new do
json = {
"text": "Here is your 200 response immediately slack",
}
render(json: json)
end
puts "--------------------------------Json response started----------------------"
sleep 30
puts "--------------------------------Json response completed----------------------"
puts "this is a successful response"
end
When I tested it the same issue happened so I tried using an online API tester and it hits the page, waits 30 seconds and then returns the 200 response but I need it to respond immediately with the 200, THEN process the rest otherwise I will get duplicates.
Am I using threads properly or is there another way to get around this Slack API 3 second response limit? I am new to both rails and slack API so a bit lost here.
Appreciate the eyes :)
I would recommend using ActionJob to run the code in the background if you don't need to use the result of the code in the response. First, create an ActiveJob job by running:
bin/rails generate job do_stuff
And then open up the file created in app/jobs/do_stuff_job.rb and edit the #perform function to include your code (so the puts statements and sleep 30 in your example). Finally, from the controller action you can call DoStuff.perform_later and your job will run in the background! Your final controller action will look something like this:
def events
DoStuff.perform_later # this schedules DoStuff to be done later, in
# the background, so it will return immediately
# and continue to the next line.
json = {
"text": "Here is your 200 response immediately slack",
}
render(json: json)
end
As an aside, I'd highly recommend never using Thread.new in rails. It can create some really confusing behavior especially in test scripts for a number of reasons, but usually because of how it interacts with open connections and specifically ActiveRecord.

stripe_event 404 error for test data, figuring out how to add stripe-account-header

I'm using stripe to verify a profile before any transactions happen, so I want to see webhooks that have type account.updated and check the verification.status.
Im receiving a 404 error '(Status 404) (Request req_AwRiJSFxaOn1Jq) No such event: evt_1AaW9jFzjmmh0zTvsNSlfDLv
I realize that i need a stripe account header for the specific account that the webhook is being made for. I cant figure out how to add an account header to my stripe.rb code. I saved the stripe_account_id in the database on account creation so i can pull that id out from the database. The part that confuses me is when the webhook sends to my app the account_id is in the webhook, so it seems I have to pull that part out of the webhook and paste into stripe.rb {:stripe_account => CONNECTED_STRIPE_ACCOUNT_ID}. how is this possible?
This is the webhook that was sent from stripe in console (this is test data so i dont mind that people can see this cause its getting deleted soon anyways)
stripe.rb
require 'stripe'
Rails.configuration.stripe = {
:publishable_key => ENV["STRIPE_PUBLISHABLE_KEY"],
:secret_key => ENV["STRIPE_SECRET_KEY"]
}
Stripe.api_key = ENV["STRIPE_SECRET_KEY"]
StripeEvent.configure do |events|
events.all do |event|
# target specific events here
puts "this is working so far"
if event.type == 'account.updated'
account_event = event.data.object
puts "account updated working"
puts "#{account_event}"
end
end
end
I received the webhook in stripe connected account dashboard and my app console.
You're likely missing the Stripe-Account header, so it's trying to fetch the Event from your Account, rather than from the Stripe Account in which it exists.
EDIT: Sorry, I totally misunderstood your question. I think you need to configure the StripeEvent receiver to use the Stripe Account.

Rails controller - execute action only if the a Rails UJS method inside succeed (mutually dependent methods)

Following another question (Rails controller - execute action only if the two methods inside succeed (mutually dependent methods)), I would like to ensure that inside one of my controller's action, if the user does not see the message displayed by a Rails UJS method, then the first methods of the controller action are not implemented either.
CONTEXT
I have a Controller with a method called 'actions'. When the app goes inside the method 'example_action', it implements a first method (1) update_user_table and then (2) another update_userdeal_table. (both will read and write database) and then (3) a third method which is related to a Rails UJS(ajax) call.
My issue is the following: in case of timeout in the middle of the controller, I want to avoid the case where the User table (via method 1) is updated, the UserDeal table is updated (via method 2) but NOT the thrid method i.e the ajax request that displays a message FAILS (error, timeout,...status like 500 or 404 or canceled or timeout...).
In my app, for mobile users if they're in a subway with internet connection, they launch the request that goes through 'example_action' controller, performs successfully the first method (1) and second method (2) but then they enter a tunnel for 60 seconds with very very low (<5b/sec) or NO internet connection, so for UX reasons, I timeout the request and display to the user 'sorry it took too long, try again'. The problem is that if I could not show to them the result (3), I need to be able to not execute (1) and(2).
I need the two methods (1) and(2) and(3) to be "mutually dependent": if one does not succeed, the other one should not be performed. It's the best way I can describe it.
Today Here is my code. It's not working as I am manually testing by clicking and then after just 2 seconds I disconnect the internet connection. I see in my database that (1) and(2) were performed and the databases were updated but I saw the message 'sorry it took too long, try again'.
Is that the right approach ? if yes how to do this?
If not, should I try a different angle like: if (1) and(2) were successful but not(3) should I store the fact the rails UJS xhr status was an error or timeout, that consequently the modal wxas not effectively displayed to the user and then show to them the result/message once they get back online?
Here is the code
html page for the user
the user click on a button that triggers a Rails UJS aajax request that will display ultimately the modal message
<div id=zone">
<%= link_to image_tag(smallest_src_request),
deal_modal_path,
remote: true %>
</div>
This send to a route that points to this controller action
Deal controller
class DealsController < ApplicationController
def deal_modal
Deal.transaction do
update_user_table # that's the (1)
update_userdeal_table # that's the (2)
# show_modal_message
respond_to do |format|
format.js
end
end
private
def update_user_table
# update the table User so it needs to connect to internet and acces the distant User table
end
def update_userdeal_table
# update the table UserDeal table so it needs to connect to internet and access the distant UserDeal table
end
end
This points to a js.erb view file
deal_modal.js.erb
showModalMessage("Here is your result <variable and all>);
To manage the ajax, error, timeouts... (if necessary to the resolution of the question), I use Rails UJS settings.
IMPORTANT: It is here that in case of error or timeout, I send the error / timeout modal message that comes in place of the one you normally get (see just above "Here is your result..")
$(document).on('page:change', function () {
$("#zone").
on('ajax:error',function(event,xhr, status, error){
console.log(' ajax call failed:', error);
var msg;
msg = Messenger().post({
hideAfter: 4,
message: "sorry it took too long, try again."
});
});
$(document).on('page:change', function () {
//set timeout Rails UJS ajax option that will display message for ajax:error cases defined above
$.rails.ajax = function(options) {
if (!options.timeout) {
options.timeout = 5000;
}
return $.ajax(options);
};
});
So the transaction will only rollback if an error is thrown. If an unhandled error is thrown, your application will crash and it will show a 500 error in some way.
In order to display the response to the user, on success or error, you will need to render something. So you don't want to prevent the respond_to block from executing. One way to handle this would be to set a flag via an instance variable.
def deal_modal
begin
Deal.transaction do
update_user_table
update_userdeal_table
end
#success = true
rescue
#success = false
end
# show_modal_message
respond_to do |format|
format.js
end
end
Then in deal_modal.js.erb
<% if #success %>
showModalMessage("Here is your result <variable and all>");
<% else %>
showModalMessage("There was a problem");
<% end %>
EDIT:
Dealing with connection issues is definitely tricky and there isn't really one ideal solution. I would generally let the database continue uninterrupted and let it return either a success or failure on it's own time. For lengthy transactions, you can use a gem like delayed_job or sidekiq to process the action in the background and let the rails controller return a response saying "...pending..." or something. Unless you're using websockets on the frontend, this means continually polling the server with ajax requests to see if the background process is complete.

How to receive data in ActionCable Channel without JS?

I'm writing a Rails application that uses WebSockets to communicate with other machines (no browser and client side logic in this process). I have a channel:
class MachinesChannel < ApplicationCable::Channel
def subscribed
...
end
def unsubscribed
...
end
def handle_messages
...
end
end
To receive the data the only way I know about is the JavaScript client:
ActionCable.createConsumer('/cable').subscriptions.create 'MachinesChannel',
received: (message) ->
#perform('handle_messages')
I can call server side methods from JS via #perform() method.
Is there any way to omit the JS part and somehow directly handle the incoming data in MachinesChannel?
The ideal situation would be to have the handle_messages method accept a data argument and have this metod called on incoming data.
After looking into ActionCable source code I got the following solution. You just have to create a method in MachinesChannel that you want to be called, e.g. handle_messages(data). Then, in the client that connects to your websocket, you need to send a message in the following format (example in ruby):
id = { channel: 'MachinesChannel' }
ws = WebSocket::Client::Simple.connect(url)
ws.send(JSON.generate(command: 'message', identifier: JSON.generate(id), data: JSON.generate(action: 'handle_messages', foo: 'bar', biz: 'baz')))
action has to be the name of the method you want to be called in MachinesChannel. The rest of key-values are whatever you want. This the date you can receive in the ActionCable channel.
Recently a gem action_cable_client has been release which seems exactly perfect for this kind of usage. I haven't used it, so I don't know how it really works.
Instead of:
def handle_messages
...
end
This works for me:
def receive(data)
puts data
...
end

Paypal Adaptive payments on Rails giving false Success

I am using the official Paypal Ruby adaptive payments gem in conjunction with the embedded payment flow to collect one time payments on my site.
The payment itself is working correctly, however in my callback I wish to check if the payment went through. When I query the payment using the code suggested here, success is true even if I cancel the payment.
require 'paypal-sdk-adaptivepayments'
#api = PayPal::SDK::AdaptivePayments::API.new
# Build request object
#payment_details = #api.build_payment_details({
:payKey => "AP-xxxxxxxxxxxxxxxx" })
# Make API call & get response
#payment_details_response = #api.payment_details(#payment_details)
# Access Response
if #payment_details_response.success?
# Comes in here every time as #payment_details_response.success? always = true
else
#payment_details_response.error
end
How do I go about checking if the payment was cancelled? (By cancelled the payment I mean click the X on the popup)
Update:
I've found a workaround for now, but its strange how the success? is always true
if #payment_details_response.paymentInfoList.paymentInfo[0].transactionStatus
&& #payment_details_response.paymentInfoList.paymentInfo[0].transactionStatus == "COMPLETED"
#success
else
#fail
end
With the Adaptive Payments platform you have to sort of think about things in 2 parts:
API Call
Transaction(s)
The API call could very well be successful while the transaction(s) is not completed, and that seems to be what you're running into here, so what you've done would be correct in my opinion.

Resources