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.
Related
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>
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
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)
Here's more of an academic question for you guys. Say I want to create a model in a ruby on rails app to track simple views information. I would like to record the user_id, the URI for the page viewed, and keep track of the number of times the user has visited a page.
Model A: One way to do this would be to create a model View with attributes user_id and page (records the uri), and then create a new entry every time a user opens a page.
Model B: A second way to do this would be to add an attribute "page_views" to the model, to track the number of times the user has accessed that page.
Pros and Cons: Model A would have more information recorded and lead to a larger db than Model B. However, Model B would require that a controller search for an existing user-page combination, and either add views to that entry, or create a new one. This leads to a smaller database, but may be worse in scale due to the need to search for existing entries.
So my question to you guys is: which is more important? Are my thoughts wrong? Am I missing something here (other performance considerations overlooked?)
NoSQL approach to tracking user activity:
model app/models/user.rb
class User < ActiveRecord::Base
include UserModules::Tracker
...
end
mixin app/models/user_modules/tracker.rb
module UserModules
module Tracker
def get
key = "user_" + self.id.to_s
arr = Resque.redis.lrange(key, 0, -1)
arr.map{|i| JSON.parse(i)}
end
def put(controller_name, action_name, details="")
key = "user_" + self.id.to_s
created = Time.now.to_formatted_s(:db)}.to_json
# silent exception handle, so you can do not run Redis localy
begin
Resque.redis.rpush key, {
:controller_name => controller_name,
:action_name => action_name,
:details => details,
:created_at => created
rescue
nil
end
end
end
end
controller app/controller/dashboard.rb
class Dashboard < ApplicationController
after_filter :track, :only => :show
# this action will be tracked
def show
end
# this action will show tracking
def logs_show
render :json => current_user.get
end
...
private
def track
details = "any details...."
current_user.put(controller_name, action_name, details)
end
end
You need to have Redis installed, I prefer to use Resque as common way to setup and initialize Redis via Resque.redis, because it will help you to browse your tracking with resque-web
On of the way to setup Redis is in my gist
The idea is as follows: when visiting a purchase page, the pre-initialized (using before_filter) #purchase variable receives save if the item in question is not free.
I make two gets, one for a paid item and one for a free item. purchase.expects(:save).returns(true) expects :save to be called only once, so the below test works.
But this is really really ugly. The test it incredibly lengthy. What would be a better way to do this? Should I mock the find_or_initialize method? If so, how would I set the #purchase instance variable?
Sorry for the ugly code below...
def test_new_should_save_purchase_if_not_free
user = users(:some)
purchase = user.purchases.build
#controller.stubs(:current_user).returns(user)
purchases_mock = mock
user.stubs(:purchases).returns(purchases_mock)
purchases_mock.stubs(:build).returns(purchase)
purchase.expects(:save).returns(true)
get :new, :item_id => items(:not_free).id, :quantity => 10
get :new, :item_id => items(:free).id, :quantity => 400
end
def new
#purchase.quantity = params[:quantity]
#purchase.item = Item.find(params[:item_id])
unless #purchase.item.free?
#purchase.save
end
end
def find_or_initialize
#purchase = params[:id] ? current_user.purchases.find(params[:id]) : current_user.purchases.build
end
It looks like you're already using fixtures for your items, why not just use a fixture for the Purchase as well? There is no need to go through all that effort to stub the user.