Stripe - not updating User's information in database - ruby-on-rails

I'm working on my first project using stripe. I've got a subscription product and I've got the basic stripe funcationality working, but I need to update my user's record in the database as being "subscribed" so that I may use it as a validation later on in development. I've seen several tutorials online which show adding a column to your model called subscribed or something along those lines and updating it during the stripe customer creation process. I've got that working, except it is not updating my user model (in this case, supplier). Here's my controller for the stripe processs:
class ChargesController < ApplicationController
before_filter :authenticate_supplier!
def new
end
def create
# Amount in cents
token = params[:stripeToken]
customer = Stripe::Customer.create(
:source => token, # obtained from Stripe.js
:plan => "Free_month_annual",
:email => current_supplier.email,
#:coupon => "coupon_ID"
)
current_supplier.subscribed = true
#current_supplier.stripe_id = token
redirect_to orders_path
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to charges_path
end
end
As you can see, there are two things commented out - a coupon, which I plan to work on later, and stripe_id update because I ran into an error for having already used the token once (can't use it twice). I've updated my supplier model, there is an appropriate column in the database, the params have been updated to allow supplier.subscribed. I'm sure this is a quick answer for many, but I need help spotting my problem.
Edit:
subscribed is a boolean field - fyi

It seems you forgot to save the current_supplier record.
The solution should be adding something like:
current_supplier.subscribed = true
#current_supplier.stripe_id = token
current_supplier.save!
redirect_to orders_path

Related

Ruby on Rails: Should I move this code into the model? If so, what's the best way?

I'm new to rails. I have a relatively simple question. I have defined a controller that manages friend requests. In the create action, I check to see if the other user has already sent a friend request to the current user. If so, I skip creating another friend request and simply execute the logic that accepts the friend request that already exists. Here is my code:
class FriendRequestsController < ApplicationController
before_filter :authenticate_user!
def create
current_user_id = current_user.id;
recipient_id = params[:recipient_id].to_i;
# check if the other person has already sent a friend request
unless (existing_request = FriendRequest.find_by(
:sender_id => recipient_id,
:recipient_id => current_user_id)).nil?
accept(existing_request)
return redirect_to current_user
end
request = FriendRequest.new(:sender_id => current_user_id,
:recipient_id => recipient_id)
if request.save
flash[:notice] = "Sent friend request."
else
flash[:errors] = request.errors.full_messages
end
redirect_to users_path
end
Should some of the above logic go into the FriendRequest model, instead? If so, how much of it? Is there a good* way I can move the call to FriendRequest.new and request.save into the model, as well, while still maintaining the necessary degree of control in the controller?
*What I mean by "good" is: standard, ruby-ish, rails-ish, easily recognizable, familiar-to-many, popular, accepted, etc.
Is there anything else about my code that stands out as poor practice?
This right here could be improved a bit:
unless (existing_request = FriendRequest.find_by(
:sender_id => recipient_id,
:recipient_id => current_user_id)).nil?
Instead, you could write a method in the friend request model:
def self.find_matching_request(sender_id, receiver_id)
find_by(sender_id: sender_id, receiver_id: receiver_id)
end
This doesn't really save you many keystrokes for this example but it's the type of thing you'd do if you wanted to move logic out of the controller and into the model. Basically making a method which is a wrapper for database interaction logic. The bulk of database interaction is supposed to be done in the model, though many people end up doing it in the controller.
Then in the controller:
existing_request = find_matching_request(current_user_id, recipient_id)
if existing_request
accept(existing_request)
redirect_to current_user
end
Before you were using unless <some_val>.nil? which looks like an antipattern unless you're treating false and nil differently for some reason. More typical would be if <some_val>

payment_state and shipment_state not persisting in spree with custom payment gateway

I'm implementing a custom payment gateway in Spree 2.2. It's one of those gateways where you redirect to the gateway's own website to take payment, and then the bank redirects back to you with a bunch of get params.
I'm having an issue where the order's payment_state and shipment_state end up as null in the database, despite the fact that they are not null in the order object itself, if I put a debugger in the code. Calling order.save doesn't seem to help.
I've implemented a dirty hack to workaround it:
# This is a hack - for some reason the payment_state and shipment_state weren't being persisted
# and where being stored in the database as null. Really the spree checkout process
# should take care of this and we shouldn't have to set them manually.
# We must be doing something wrong...
order.update_attribute :payment_state, 'paid'
order.update_attribute :shipment_state, 'ready'
But I'd really like to know what the actual issue is - why aren't those states being persisted? (I should add, before I call the code above, the values for order.payment_state and order.shipment_state respectively are balance_due and pending - but that's another issue. If I can get them to save in any way, that's the main issue.
Any ideas what I'm doing wrong?
Full code for my controller and gateway is below.
class Spree::CommBankController < Spree::StoreController
def secure_payment
order = current_order
#order_info = 'Espionage Online order ' + order.number
payment_params = {
"Title" => 'Espionage Online',
"vpc_AccessCode" => payment_method.preferred_access_code,
"vpc_Amount" => (order.total * 100).to_i, # convert to cents
"vpc_Desc" => #order_info,
"vpc_MerchTxnRef" => order.number,
"vpc_Merchant" => payment_method.preferred_merchant_id_no,
"vpc_OrderInfo" => #order_info,
"vpc_ReturnURL" => secure_payment_callback_url(payment_method_id: payment_method.id),
}
payment_request = ::CommWeb::PaymentRequest.new(payment_params, payment_method.preferred_secure_secret)
redirect_to payment_request.url
end
def secure_payment_callback
# Next line - see http://stackoverflow.com/questions/4116545/how-do-i-get-only-the-query-string-in-a-rails-route
order = current_order
query_params = params.except(*request.path_parameters.keys)
payment_response = ::CommWeb::PaymentResponse.new(query_params, payment_method.preferred_secure_secret)
if !secure_hash_matches?(payment_response)
flash.notice = 'Error with payment - secure hash did not match. Please try again.'
redirect_to checkout_state_path(order.state)
return
end
payment = order.payments.create!({
:source => Spree::CommbankCheckout.create({
:params_hash => payment_response.params.to_s,
:success => payment_response.success?,
:desc => payment_response.description,
:trx_response_code => payment_response.trx_response_code,
:message => payment_response.message,
}),
:amount => order.total,
:payment_method => payment_method,
:response_code => payment_response.trx_response_code,
})
payment.pend
if payment_response.success?
# Set payment to completed after order.next because
# spree expects at least one incomplete payment to process an order to complete
order.next!
payment.complete
debugger
# This is a hack - for some reason the payment_state and shipment_state weren't being persisted
# and where being stored in the database as null. Really the spree checkout process
# should take care of this and we shouldn't have to set them manually.
# We must be doing something wrong...
order.update_attribute :payment_state, 'paid'
order.update_attribute :shipment_state, 'ready'
else
payment.failure
end
if order.complete?
flash.notice = Spree.t(:order_processed_successfully)
session[:order_id] = nil
redirect_to completion_route(order)
else
flash.notice = 'Error: ' + payment_response.message + '. Please try again.'
redirect_to checkout_state_path(order.state)
end
end
def secure_hash_matches? payment_response
payment_response.secure_hash_matches?
end
def payment_method
#payment_method ||= Spree::PaymentMethod.find(params[:payment_method_id])
end
def completion_route(order)
order_path(order)
end
end
and the gateway...
# Partly inspired from https://github.com/spree-contrib/spree-adyen (the hosted payment page class)
module Spree
class Gateway::CommBank < Gateway
preference :merchant_id_no, :string
preference :access_code, :string
preference :secure_secret, :string
def auto_capture?
true
end
# Spree usually grabs these from a Credit Card object but when using
# Commbank's 3 Party where we wouldn't keep the credit card object
# as that's entered outside of the store forms
def actions
%w{capture}
end
# Indicates whether its possible to void the payment.
def can_void?(payment)
!payment.void?
end
# Indicates whether its possible to capture the payment
def can_capture?(payment)
payment.pending? || payment.checkout?
end
def method_type
'commbank'
end
def capture(*args)
ActiveMerchant::Billing::Response.new(true, "", {}, {})
end
def source_required?
false
end
def provider_class
self.class
end
def provider
self
end
def purchase
# This is normally delegated to the payment, but don't do that. Handle it here.
# This is a hack copied from the Spree Better Paypal Express gem.
Class.new do
def success?; true; end
def authorization; nil; end
end.new
end
end
end
Check order.state_changes. Do they show changes to the two states?
I am encountering the same issue while using "spree-adyen". The order.state_changes shows that the payment_state and shipment_state have changed to ready. However, it doesn't persist in the order. This happens randomly with 10% of the orders. I am currently calling order.update! manually on the such order, but would really like to know as well what the issue is.
Also, I am not quite sure if order.update! is a good solution, as it executes a lot of queries and can be very expensive.
Umm. So apparently order.update! will solve my issue. Woops.
Still, a call to order.update! isn't something I've seen in other Spree Payment Gateway gems (https://github.com/spree-contrib/better_spree_paypal_express or https://github.com/spree-contrib/spree-adyen), so I'd be interested to know if I'm doing something really stupid. (I ended up noticing it in the code of https://github.com/coinbase/coinbase-spree/blob/master/app%2Fcontrollers%2Fspree%2Fcoinbase_controller.rb)

Custom Devise/Warden strategy that requires intermediate step with user interaction

I have a Spree application and I’m trying to implement a custom authentication strategy with Devise/Warden. The idea is that users are mapped to one or more companies. The first step in authenticating a user is to hit an external api endpoint that returns a list of associated companies. If no companies are returned, the user is invalid and I call fail!. However, if one or more companies are returned, my goal is to require the user to select an active company to use before they are authenticated and can continue to the site. Here is what I've got so far:
def valid?
params[:email]
end
def authenticate!
email = params[:email]
companies = get_user_companies(email)
if (companies.length > 0)
user = User.find_by :email => email
# have user select the active company here
success!(user)
else
fail!("User has no companies")
end
end
def get_user_companies(email)
url = "http://localhost/user/#{email}/companies"
JSON.parse(RestClient.get url, :content_type => :json, :accept => :json)
end
A user is not valid without a company, and the active company cannot be changed during a session. Is this possible? How would I go about implementing this? Would this step make more sense outside of an auth strategy? If so, where? Thanks!

Devise/OmniAuth not signing in some users

Our product is a Rails application; authentication is handled with Devise and OmniAuth. ~2000 users total. We've recently had reports of some users not being able to sign in, but we can't figure out why. Not getting any server errors or anything in our production logs to suggest anything is awry.
Let's look at some code…
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
...
def twitter
oauthorize "twitter"
end
private
def oauthorize(provider)
if env['omniauth.auth']
#identity = Identity.from_omniauth(env['omniauth.auth'])
#person = #identity.person
# 1. failing here? Maybe?
if #person
PersonMeta.create_for_person(#person, session[:referrer], session[:landing_page]) if #person.first_visit?
# 2. PersonMetas *aren't* being created.
flash[:notice] = I18n.t("devise.omniauth_callbacks.success", kind: provider)
sign_in_and_redirect(#person, :event => :authentication)
# 3. Definitely failing by here…
else
redirect_to root_url
end
else
redirect_to root_url
end
end
end
class Identity < ActiveRecord::Base
belongs_to :person, counter_cache: true, touch: true
after_create :check_person
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first_or_initialize.tap do |identity|
identity.oauth_token = auth['credentials']['token']
identity.oauth_secret = auth['credentials']['secret']
case auth['provider']
when "twitter"
identity.name = auth['info']['name']
identity.nickname = auth['info']['nickname']
identity.bio = auth['info']['description']
identity.avatar_address = auth['info']['image']
else
raise "Provider #{provider} not handled"
end
identity.save
end
end
def check_person
if person_id.nil?
p = create_person(nickname: nickname, name: name, remote_avatar_url: biggest_avatar)
p.identities << self
end
end
def biggest_avatar
avatar_address.gsub('_bigger', '').gsub('_normal', '') if avatar_address
end
end
class PersonMeta < ActiveRecord::Base
attr_accessible :landing_page, :mixpanel_id, :referrer_url, :person_id
belongs_to :person
def self.create_for_person(person, referrer, landing_page)
PersonMeta.create!(referrer_url: referrer, landing_page: landing_page, person_id: person.id)
end
end
So we have that, and we're not getting any errors in production.
Where do we start? Well, let's see if the point of failure is Identity.from_omniauth
This method searches for an existing identity (we've written extra code for more providers, but not implemented client-side yet). If no identity is found it will create one, and then create the associated Person model. If this was the point of failure we'd be able to see some suspiciously empty fields in the production console. But no - the Person & Identity models have all been created with all of the correct fields, and the relevant bits of the app have seen them (e.g. their 'user profile pages' have all been created).
I just added in the if #person to the #oauthorize - we had one 500 where #identity.person was nil, but haven't been able to replicate.
Anyway, the real-world users in question do have complete models with associations intact. Moving down the method we then create a PersonMeta record to record simple stuff like landing page. I'd have done this as an after_create on the Person but I figured it wasn't right to be passing session data to a model.
This isn't being created for our problematic users. At this point, I'm kind of stumped. I'm not sure how the create ! (with bang) got in there, but shouldn't this be throwing an exception if somthing's broken? It isn't.
That is only called if it's a person's first visit anyway - subsequent logins should bypass it. One of the problematic users is a friend so I've been getting him to try out various other things, including signing in again, trying different browsers etc, and it keeps happening
so anyway, after spending 45 minutes writing this post…
One of the users revoked access to the app via Twitter and reauthenticated. Everything works now.
What the hell?
His old identity had his OAuth tokens etc stored properly.
Luckily this is resolved for one user but it's obviously an ongoing problem.
What do we do?
Is it possible that the identity.save line in Identity.from_omniauth is failing silently? If so, your after_create hook won't run, #identity.person will be nil, and you'll just (silently) redirect.
Try identity.save! ?

Paypal Express ActiveMerchant integration

I am following Ryan Bates' railcast 146 and it is really helpful. However, Im trying to remove the cart object from the process, and just process an order individually. The problem I am having is how to establish the amount which is used twice: once to setup the purchase, and then once to actually execute it. This is what I have resorted to doing, but it exposes the amount in the return_url, which I think is probably bad practice:
class OrdersController < ApplicationController
def express
response = EXPRESS_GATEWAY.setup_purchase(params[:amount],
:ip => request.remote_ip,
:return_url => new_order_url(:amount=>params[:amount]),
:cancel_return_url => root_url
)
redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token)
end
def new
#order = Order.new(:express_token => params[:token], :price_in_cents=>params[:amount])
end
Then in the view, I add a hidden field with the amount so that when the order is created it has the amount built in (I added a price_in_cents field to the order model). It works fine, but exposing the amount as a param may be a little iffy. Finally, the purchase code looks like this:
def purchase
response = process_purchase
transactions.create!(:action => "purchase", :amount => price_in_cents, :response => response)
cart.update_attribute(:purchased_at, Time.now) if response.success?
response.success?
end
In short, how can I do this without passing around the amount in the params?
Thanks!
Sending the amount in the url is very bad practice - It allows one to change the price and purchase what ever you are selling for the amount he specifies in the URL.
I can see two ways around this issue:
1. You can encrypt the parameters you pass, and decrypt them from the URL. See how to encrypt here
2. You can create a new entity that holds the price of the purchase (or in case you are selling a specific item (since you are not using a cart) - you can pass the id of this item and query for it's price when you need it). Something like this:
class OrdersController < ApplicationController
def express
#product = Product.find(params(:product_id));
response = EXPRESS_GATEWAY.setup_purchase(product.price_in_cents,
:ip => request.remote_ip,
:return_url => new_order_url(product.price_in_cents),
:cancel_return_url => root_url
)
redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token)
end
def new
#product = Product.find(params(:product_id));
#order = Order.new(:express_token => params[:token], :price_in_cents=> #product.price_in_cents)
end
Thanks for your input guys. I ended up storing the amount in the user's session, something like session[:amount], and then setting it to nil as soon as they finish the process. That way its hidden from the user and saves me the trouble of creating new objects or encrypting.

Resources