I am integrating a custom pay method into my rails app. But I cannot do that my order goes from payment stat to completed state. I am ensure that the payment registered at the order is at completed state, so I cannot figure out why the state machine are telling me "No payment found"
Here's my code.
def perform_payment payment_id, state
Rails.logger.info("PERFORM PAYMENT => payment_id: #{payment_id}, state: #{state}")
payment = Spree::Payment.find payment_id
return unless payment
order = payment.order
begin
if state == "accepted"
payment.started_processing!
payment.capture!
Rails.logger.info("PERFORM PAYMENT => order_id:#{order.id}, current_order_state: #{order.state}")
Rails.logger.info("PERFORM PAYMENT => order_payments:#{order.payments.size}")
order.next! unless order.state == "completed"
elsif state == "rejected"
payment.started_processing!
payment.failure!
end
rescue Exception => e
Rails.logger.error("Error al procesar pago orden #{order.number}: E -> #{e.message}")
return false
end
end
Here's the error that I'm getting
StateMachines::InvalidTransition (Cannot transition state via :next from :payment (Reason(s): No payment found))
Actually, It was #Alex mentions. It's only neccesary the .netx! on the order. To do that. you need to ensure that you have a payment in "completed" state.
Related
order controller page
begin
Stripe.api_key = ENV["STRIPE_API_KEY"]
token = params[:stripeToken]
charge = Stripe::Charge.create(
:amount => (#listing.price * 100).floor,
:currency => "usd",
:source => params[:stripeToken],
:destination => #seller.recipient
)
flash[:notice] = "Thanks for ordering!"
rescue Stripe::CardError => e
flash[:danger] = e.message
end
order.js.coffee
jQuery ->
Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'))
payment.setupForm()
payment =
setupForm: ->
$('#new_order').submit ->
$('input[type=submit]').attr('disabled', true)
Stripe.card.createToken($('#new_order'), payment.handleStripeResponse)
false
handleStripeResponse: (status, response) ->
if status == 200
$('#new_order').append($('<input type="hidden" name="stripeToken" />').val(response.id))
$('#new_order')[0].submit()
else
$('#stripe_error').text(response.error.message).show()
$('input[type=submit]').attr('disabled', false)
I am not sure why the source => params[:stripeToken] is not passing thru when I check the log in my stripe account. Is the token null?
Usually when you receive this error is because the publishable key is not in your form so when you send the form to stripe it doesn´t recognize you and it doesn´t send you back the token.
The process of charging a client goes this way.
1.- In your site you tell the user to type his card information in a form (hosted locally or just created by stripe script).
2.- Once the user submit the form. The information is submitted to stripe server with your publishable key. You receive a response from the server with a token. This token is forwarded to the action in your controller and since is linked to your account you can create a customer, a charge or join the user to a plan. This is the 'params[:stripeToken]'. The card information never reaches your controller, you just receive this token.
You can do two things to charge your clients, the first is create the form your self and the second just delegate it to the stripe script.
The first one is more tricky since stripe updates their api from time to time. It requires some knowledge of js and their api. For example Stripe v3 is different from v2. Allows more rationalization but requires more knowledge of js. The information and examples of how to configure can be found in stripe elements.
The second one is pretty easy. I recommend it to not advanced users. You can implement it just following the instructions in this link using checkout and rails.
I usually use the second option since it just takes 5 minutes and you delegate the hard work to stripe.
I need help to understand how to mark user as cancelled in my database when the user cancels the recurring payment via PayPal.
Notifications controller:
def create
params.permit! # Permit all Paypal input params
#query = params
puts "in method*************************************"
#query[:cmd] = "_notify-validate"
#if params[:txn_type] == 'subscr_cancel'
# user_subscription = Subscription.find_by(paypal_customer_token: params[:payer_id])
# user_subscription.update_column("cancelled",1) if user_subscription.present?
#els
if params[:txn_type] == 'recurring_payment_profile_cancel'
user_subscription = Subscription.find_by(paypal_recurring_profile_token: params[:recurring_payment_id])
user_subscription.update_column("cancelled",1) if user_subscription.present?
end
render :text => 'OK'
end
end
Hm,, I think you have to uncommend the lines you have commented,, somehting like this:
if params[:txn_type] == 'subscr_cancel'
user_subscription = Subscription.find_by(paypal_customer_token: params[:payer_id])
user_subscription.update_column("cancelled",1) if user_subscription.present?
end
I'm using php api and have this 'subscr_cancel' when user cancels subscription
Given the following method
def change_plan_to(plan_id)
new_plan = Plan.find plan_id
stripe_customer = Stripe::Customer.retrieve(stripe_customer_token)
stripe_customer.update_subscription(plan: new_plan.slug)
self.plan = new_plan
self.active = true
save
rescue Stripe::InvalidRequestError => e
logger.error "[STRIPE] #{ e }"
errors.add :base, "Unable to change your plan!"
false
end
Specifically line #4-6. I want 4 and 5 to happen only if 4 is successful but Stripe doesn't return the ability to wrap that in a if. If it errors it just throws Stripe::InvalidRequestError.
What's the best way to handle this? Fire & forget and allow Stripe webhook callbacks to manage expiring active state as needed?
The other scenario is that all the code will halt after line 4 if it doesn't pass. Is this how rescue works?
Yes thats the way rescue work,
So better you execute these statements which is dependent on line 4 in webhook callbacks that stripe sends to you. Because that ensures subscription change.
I am working on a small app that allows for users to add a product (or subscription) to their cart. Upon creating their account, the new user is sent to a "bundle" page where it asks if they would like to add a different subscription to a different product altogether for a bundled price.
Here is where I am stuck: Upon submitting the user's credit card info I get slightly "lost in translation" when trying to setup the bundle pricing to submit to Authorize.net (I understand how to authnet, not the question here).
Here is what I have so far:
current_order.products.includes(:client).each do |product|
transaction = current_order.submit_order_to_authnet(product)
if transaction.result_code == 'Ok'
new_group = Group.create!(:name => "#{current_user.full_name} #{product.title}", :type => 'school', :start_date => Time.now, :status => 'active', :site_id => 1)
primary = session[:primary_product_id].eql?(product.id) ? true : false
# Add subscription to Group
new_group.add_subscription(product, current_order, transaction.subscription_id, 'active', primary)
# Add Subscription to CurrentOrder
current_order.subscriptions << new_group.subscriptions.last
# Add user to NewGroup
current_user.groups << new_group
# Create New Group Admin
new_group.group_admins.create(:user_id => current_user.id)
# Send success email
OrderMailer.checkout_confirmation(current_user).deliver
else
errors << transaction.result_code
end
end
I am trying to figure out the best solution when it comes to looping through each product in the users current_order because the second subscription in the users cart is the subscription that gets the discount applied too. I know I can write something like this:
current_order.products.includes(:client).each do |product|
if current_order.products.many? and product == current_order.products.last
# run discount logic
else
# continue with authnet for single subscription
end
end
But I am just not sure if that is a best practice or not. Thoughts?
So the only subscription that doesn't get discounted is the first one? Why not write it like this:
current_order.products.includes(:client).each do |product|
if product == current_order.products.first
# continue with authnet for single subscription
else
# run discount logic
end
end
I am new to rails and new to active merchant, just want to know if the following code is good enough for payment processing using active merchant.
As you can see, I am using authorize and capture instead of the purchase method. My main concern is the "brought_quantity" subtraction in the code (and it's counter part, when the payment processing fails), I am not quite sure how to deal with it in case of race condition or error from the payment gateway.
Please note that the variable transactions is a instance variable for a model/table where I store the information of the payment gateways responses.
def purchase(item)
price = price_in_cents(item.value)
if !item.can_purchase
errors[:base] << "We are sorry, all items are sold out at the moment."
return false
else
response = GATEWAY.authorize(price, credit_card, purchase_options)
transactions.create!(:action => "authorize", :value => price, :params => response)
#p response
if response.success?
item.brought_quantity = item.brought_quantity + 1
if item.save!
response = GATEWAY.capture(price, response.authorization)
transactions.create!(:action => "capture", :value => price, :params => response)
if !response.success?
errors[:base] << "There were some problem processing your payment, please either try again or contact us at support#foo.com with this error id: 111"
#rd = RunningDeal.find_by_id(#item.id)
#rd.brought_quantity = #rd.brought_quantity - 1
#rd.save!
return false
end
else
errors[:base] << "We are sorry, all items are sold out at the moment."
return false
end
else
# problem process their payment, put out error
errors[:base] << "There were some problem processing your payment, please either try again or contact us at support#foo.com with this error id: 111"
return false
end
end
return true
end
Edit
Ok, did some refactoring and here is the updated code, any comments and suggestions are welcome. I removed the ! on transaction.create since that not a important enough operation to raise an exception.
Here is the updated code based on the feedback.
#from running_deal.rb
def decrement_deal_quantity
self.brought_quantity = self.brought_quantity + 1
return self.save!
end
def purchase(running_deal)
price = price_in_cents(running_deal.value)
if !running_deal.can_purchase
errors[:base] << "We are sorry, all items are sold out at the moment."
return false
else
auth_resp = GATEWAY.authorize(price, credit_card, purchase_options)
transactions.create(:action => "authorize", :value => price, :success => auth_resp.success?, :message => auth_resp.message, :authorization => auth_resp.authorization, :params => auth_resp)
if auth_resp.success?
begin
running_deal.decrement_deal_quantity
cap_resp = GATEWAY.capture(price, auth_resp.authorization)
transactions.create(:action => "capture", :value => price, :success => cap_resp.success?, :message => cap_resp.message, :authorization => cap_resp.authorization, :params => cap_resp)
rescue
GATEWAY.void(auth_resp.authorization, purchase_options) if auth_resp.success?
errors[:base] << "There were some problem processing your payment, please either try again or contact us at support#foo.com"
return false
end
else
# problem process their payment, put out error
errors[:base] << "There were some problem processing your payment, please either try again or contact us at support#foo.com"
return false
end
end
return true
end
processing transactions is tricky.
Couple of thoughts:
if authorize succeeds, capture will succeed in 99.9% of cases. You don't really need to worry about this case that much.
if something fails in your code after authorize() was successful (like an exception writing to the database) you want to call void() to the gateway, to remove the authorization. Otherwise funds are frozen for 7 days.
this code needs to be moved into a model method:
#rd = RunningDeal.find_by_id(#item.id)
#rd.brought_quantity = #rd.brought_quantity - 1
#rd.save!
you need to add clause to the bottom of your method to catch exceptions, since you are calling create!() not create() (which return true if it saves)
rescue Exception => e
# error handing
end
it is unclear why if item.save! fails your error message indicates that the item is sold out? That is totally obscure.
Overall, you want to do someting like this:
check if there is enough inventory
perform AUTH
start db transaction
save/update all db objects
commit transaction
perform CAPTURE
catch exception, and if AUTH was successful - perform VOID
Hope this helps.