Paypal Adaptive payments on Rails giving false Success - ruby-on-rails

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.

Related

500 error for stripe webhook event checkout.session.completed

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

ActiveMerchant Cybersource Authorization Error Reason 102

I'm work on debugging an application that is currently running on Ruby 2.5.8 with ActiveMerchant 1.117.0.
I am able to create and save the subscription successfully. However, when I try to authorize the saved card I keep getting reasonCode 102. The error from the Cybersource gateway side is the subscription () could not be found.
I'm attempting to authorize with the function:
def authorize(token, amount, order_id, line_items)
response = gateway.authorize(amount, token, order_id: order_id, line_items: line_items)
if !response.success?
raise Exceptions::ChargeFailed.new(response.message, response: response)
end
response
end
The error would lead me to believe that the formatting here is not correct. Can anyone point me to some working ActiveMerchant CyberSource examples for authorizing a subscription or point out what might be wrong here?
After some digging, it appears that the logic for token assignment changed inside of the ActiveMerchant CyberSource gem
The original logic was:
if reference
_, subscription_id, _ = reference.split(";")
xml.tag! 'subscriptionID', subscription_id
end
The latest versions this shifted towards:
if reference
subscription_id = reference.split(";")[6]
xml.tag! 'subscriptionID', subscription_id
end
The application that I am working with previously had been formatting the CyberSource profile / subscription id as ;#{token};. In order to work with these latest updates the token needs to be formatted as ;;;;;;#{token}.

shopify application charge failing to save

Below is my code for a Shopify one-time-application-charge in Ruby. I followed the shopify "add billing to your app" page (https://help.shopify.com/api/tutorials/adding-billing-to-your-app) for the code, except didn't need a recurring charge. I have also found someone else who posted their one-time-charge code which looks very similar to mine (https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/one-time-application-charge-example-for-shopify-rails-app-489347).
def create_application_charge
application_charge = ShopifyAPI::ApplicationCharge.new(
name: "MyApp",
price: 0.09,
return_url: "https:\/\/myapp.herokuapp.com\/activatecharge",
test: true)
save = application_charge.save
if save
redirect application_charge.confirmation_url
return
end
flash[:error] = "The save worked: #{save}"
end
The flash always responds as false. Is there a failure at authentication that would prevent this? Or something to get the store to accept an application charge? I'm at a loss as to why this does not work.
Any help would be greatly appreciated, thank you.
The primary issue appears to be that the minimum charge you can request is $0.50, for which I wasn't meeting with my choice of using $0.09 for my test.

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.

Preventing rapid-fire login attempts with Rack::Attack

We have been reading the Definitive guide to form based website authentication with the intention of preventing rapid-fire login attempts.
One example of this could be:
1 failed attempt = no delay
2 failed attempts = 2 sec delay
3 failed attempts = 4 sec delay
etc
Other methods appear in the guide, but they all require a storage capable of recording previous failed attempts.
Blocklisting is discussed in one of the posts in this issue (appears under the old name blacklisting that was changed in the documentation to blocklisting) as a possible solution.
As per Rack::Attack specifically, one naive example of implementation could be:
Where the login fails:
StorageMechanism.increment("bad-login/#{req.ip")
In the rack-attack.rb:
Rack::Attack.blacklist('bad-logins') { |req|
StorageMechanism.get("bad-login/#{req.ip}")
}
There are two parts here, returning the response if it is blocklisted and check if a previous failed attempt happened (StorageMechanism).
The first part, returning the response, can be handled automatically by the gem. However, I don't see so clear the second part, at least with the de-facto choice for cache backend for the gem and Rails world, Redis.
As far as I know, the expired keys in Redis are automatically removed. That would make impossible to access the information (even if expired), set a new value for the counter and increment accordingly the timeout for the refractory period.
Is there any way to achieve this with Redis and Rack::Attack?
I was thinking that maybe the 'StorageMechanism' has to remain absolutely agnostic in this case and know nothing about Rack::Attack and its storage choices.
Sorry for the delay in getting back to you; it took me a while to dig out my old code relating to this.
As discussed in the comments above, here is a solution using a blacklist, with a findtime
# config/initilizers/rack-attack.rb
class Rack::Attack
(1..6).each do |level|
blocklist("allow2ban login scrapers - level #{level}") do |req|
Allow2Ban.filter(
req.ip,
maxretry: (20 * level),
findtime: (8**level).seconds,
bantime: (8**level).seconds
) do
req.path == '/users/sign_in' && req.post?
end
end
end
end
You may wish to tweak those numbers as desired for your particular application; the figures above are only what I decided as 'sensible' for my particular application - they do not come from any official standard.
One issue with using the above that when developing/testing (e.g. your rspec test suite) the application, you can easily hit the above limits and inadvertently throttle yourself. This can be avoided by adding the following config to the initializer:
safelist('allow from localhost') do |req|
'127.0.0.1' == req.ip || '::1' == req.ip
end
The most common brute-force login attack is a brute-force password attack where an attacker simply tries a large number of emails and passwords to see if any credentials match.
You should mitigate this in the application by use of an account LOCK after a few failed login attempts. (For example, if using devise then there is a built-in Lockable module that you can make use of.)
However, this account-locking approach opens a new attack vector: An attacker can spam the system with login attempts, using valid emails and incorrect passwords, to continuously re-lock all accounts!
This configuration helps mitigate that attack vector, by exponentially limiting the number of sign-in attempts from a given IP.
I also added the following "catch-all" request throttle:
throttle('req/ip', limit: 300, period: 5.minutes, &:ip)
This is primarily to throttle malicious/poorly configured scrapers; to prevent them from hogging all of the app server's CPU.
Note: If you're serving assets through rack, those requests may be counted by rack-attack and this throttle may be activated too quickly. If so, enable the condition to exclude them from tracking.
I also wrote an integration test to ensure that my Rack::Attack configuration was doing its job. There were a few challenges in making this test work, so I'll let the code+comments speak for itself:
class Rack::AttackTest < ActionDispatch::IntegrationTest
setup do
# Prevent subtle timing issues (==> intermittant test failures)
# when the HTTP requests span across multiple seconds
# by FREEZING TIME(!!) for the duration of the test
travel_to(Time.now)
#removed_safelist = Rack::Attack.safelists.delete('allow from localhost')
# Clear the Rack::Attack cache, to prevent test failure when
# running multiple times in quick succession.
#
# First, un-ban localhost, in case it is already banned after a previous test:
(1..6).each do |level|
Rack::Attack::Allow2Ban.reset('127.0.0.1', findtime: (8**level).seconds)
end
# Then, clear the 300-request rate limiter cache:
Rack::Attack.cache.delete("#{Time.now.to_i / 5.minutes}:req/ip:127.0.0.1")
end
teardown do
travel_back # Un-freeze time
Rack::Attack.safelists['allow from localhost'] = #removed_safelist
end
test 'should block access on 20th successive /users/sign_in attempt' do
19.times do |i|
post user_session_url
assert_response :success, "was not even allowed to TRY to login on attempt number #{i + 1}"
end
# For DOS protection: Don't even let the user TRY to login; they're going way too fast.
# Rack::Attack returns 403 for blocklists by default, but this can be reconfigured:
# https://github.com/kickstarter/rack-attack/blob/master/README.md#responses
post user_session_url
assert_response :forbidden, 'login access should be blocked upon 20 successive attempts'
end
end

Resources