Shopping cart with ruby on rails. I inherited a project and had to learn rails from scratch to manage it. It's going okay but this one has me stumped. Any inputs are much appreciated. Thanks!
Some of the users experience the problem that when they return from the payment processor after making a successful purchase, their browser wipes cookies clean (I assume some security implementation on a browser update / anti virus etc.) When they arrive back at the site, a new session is created automatically. This seems to be a random occurrence but after investigation I found I can simulate the issue by deleting the session cookies for the cart with browser tools before clicking through to "pay now".
As it was setup, the system has no way of telling who the returning user is when the original cookie is missing. The page fails to render, causing their transaction to still show as unpaid, even though the payment was successful and money subtracted from their bank account. The user just sees a blank screen in this case. I've setup error reporting, and this is what I get:
A NoMethodError occurred in payments#success:
undefined method
id' for nil:NilClass app/controllers/payments_controller.rb:130:in
block (2 levels) in success'
This makes sense, because the system is unable to retrieve the user id, or in this case get @user.id from current_user.id because I assume current_user is not defined if the correct authenticated cookie is missing.
POST variables received back from payment processor:
Parameters : {"PAY_REQUEST_ID"=>"51A3B42C-573E-085D-EE38-82E03A2817A3", "TRANSACTION_STATUS"=>"1", "CHECKSUM"=>"c38ba5ad8e69dad4b6cef26eb0831089", "controller"=>"payments", "action"=>"success"}
I thought long and hard about this, and spent about 6 hours trying to get it to work.
Logically, I think the solution must be along these lines:
The results from the payment processor returns a variable that I could use to find the transaction in the database. PAY_REQUEST_ID is a value stored in the payments sql table.
The payments table also has the user_id column, so I can use it to get the correct user id:
@payment = Payment.where('authorisation = ?', params[:PAY_REQUEST_ID]).first
user_id = @payment.user_id
I changed the session storage from browser to database, so there is a table with the sessions where I could retrieve the information from.
My question then: How can I retrieve the correct session from the session table in database and load it by using the extracted @user.id as a search key? (If it can create the correct "reference" cookie in the browser automatically, so ruby knows which session to fetch from the database, that would be perfect!)
Here's an example of the incorrect session created after cookies were deleted by the browser.
Session:
- session id: [FILTERED]
- data: {"session_id"=>"197af40fddddb7811a6bd0af5641c498", "currency"=>"USD"}
I've tried to repopulate current_user with something like this: current_user = User.where('id = ?', @payment.user_id) but it seems the current_user I create like that is off a different "type" or data structure than what the correct current_user variable is supposed to be, so this causes further issues and the code breaks.
As mentioned, in the code, there is a line @user = current_user and later it's called voucher.user_id = @user.id which triggers the noMethod error if the session cookie was wiped by browser.
The simplest solutions are the best solutions. I struggled to define current_user and also to log the user in automatically. In my question, I learned lots about how rails work, and I also realized that it's not needed to re-invent the wheel.
Reading through the session_controller.rb code, where sessions are created and destroyed I saw that two session values are set by the "new" method. The one session variable is the Auth token and the other is the user's default currency. I know the auth token is already stored in the users table, so it's very easy to get @user = User.find_by_id(@payment.user_id) and then get the auth token with @user.auth_token
current_user = User.where('id = ?', @payment.user_id)
@user = User.find_by_id(@payment.user_id)
session[:currency] = @user.currency_ind
session[:auth_token] = @user.auth_token
Once the session[:auth_token] is set, the user is logged in automatically, and it's business as usual. No need for funny business.
Hopefully this will help somebody else who gets stuck with the same problem.