21

There are official code samples for several languages but couldn't find one for Rails.

joscas
  • 7,474
  • 5
  • 39
  • 59

7 Answers7

31

I post here my working code sample for a Rails controller. It does verification. I hope it will be useful.

class PaymentNotificationsController < ApplicationController
  protect_from_forgery :except => [:create] #Otherwise the request from PayPal wouldn't make it to the controller
  def create
    response = validate_IPN_notification(request.raw_post)
    case response
    when "VERIFIED"
      # check that paymentStatus=Completed
      # check that txnId has not been previously processed
      # check that receiverEmail is your Primary PayPal email
      # check that paymentAmount/paymentCurrency are correct
      # process payment
    when "INVALID"
      # log for investigation
    else
      # error
    end
    render :nothing => true
  end 
  protected 
  def validate_IPN_notification(raw)
    live = 'https://ipnpb.paypal.com/cgi-bin'
    sandbox = 'https://ipnpb.sandbox.paypal.com/cgi-bin'
    uri = URI.parse(sandbox + '/webscr?cmd=_notify-validate')
    http = Net::HTTP.new(uri.host, uri.port)
    http.open_timeout = 60
    http.read_timeout = 60
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
    http.use_ssl = true
    response = http.post(uri.request_uri, raw,
                         'Content-Length' => "#{raw.size}",
                         'User-Agent' => "My custom user agent"
                       ).body
  end
end

Code is inspired by Railscast 142 and this post by Tanel Suurhans

joscas
  • 7,474
  • 5
  • 39
  • 59
  • 5
    Really helpful, thanks! although just as a note one must use OpenSSL::SSL::VERIFY_PEER if they really want it to be secure – Mohamed Hafez Dec 24 '13 at 05:10
  • 3
    Well this saved me a considerable chunk of my afternoon. I can go home early now. Thanks very much! – superluminary Apr 28 '14 at 15:34
  • This saved me a lot of time, thank you! Also, remember to use the SANDBOX paypal URL for verification, when not in production mode! (https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate) – Tom Lord Aug 20 '15 at 09:09
  • Oh, and regarding "check that txnId has not been previously processed", there is an important gotcha here. If a "Pending" payment status is received before the "Completed" payment status, then these will both have the same transaction ID. In other words, to clarify, you must validate that only one "Completed" notification is received with that txn_id. – Tom Lord Aug 20 '15 at 09:12
  • I tried with this code (I do masspay), and received from Paypal #. Does anyone know what this means? – kong Feb 24 '16 at 23:44
  • 1
    The url has now changed, I've been messing with this half a day before I realized the problem. Here are the new urls: `live = 'https://ipnpb.paypal.com/cgi-bin' sandbox = 'https://ipnpb.sandbox.paypal.com/cgi-bin' uri = URI.parse(sandbox + '/webscr?cmd=_notify-validate')` – Maayan Naveh Feb 25 '19 at 13:52
  • @MaayanNaveh when was this changed? – Nathan B Jul 10 '19 at 17:44
3

PayPal's Ruby Merchant SDK provides an ipn_valid? boolean method to make this super easy on you.

def notify
  @api = PayPal::SDK::Merchant.new
  if @api.ipn_valid?(request.raw_post)  # return true or false
    # params contains the data
  end
end

https://github.com/paypal/merchant-sdk-ruby/blob/master/samples/IPN-README.md

Dave Kiss
  • 10,289
  • 11
  • 53
  • 75
  • 1
    Be sure to add the `protect_from_forgery except: [:notify]` to your controller so that the POST is not rejected because it can't verify CSRF token authenticity. – scarver2 Aug 11 '14 at 14:17
2

IPN gem

DWilke's Paypal IPN gem can be found here:

https://github.com/dwilkie/paypal

Check out the IPN module. It's nice code:

https://github.com/dwilkie/paypal/blob/master/lib/paypal/ipn/ipn.rb

Testing against the simulator

You can test it out against the IPN simulator here:

https://developer.paypal.com/webapps/developer/applications/ipn_simulator

I use ngrok to expose localhost:3000 on a public URL, then point the simulator at it.

superluminary
  • 47,086
  • 25
  • 151
  • 148
0

I have implement IPN in one of my project and your code look fine. So what is the problem u r facing ?

Lucky
  • 140
  • 6
  • Hi Rovin. Yes, my code works fine, thanks for confirming. I've posted it here because it took me some work to come up with the working solution and since there is no official example, I thought others could benefit from it. – joscas Jan 14 '13 at 10:27
0

Have a look at the ActiveMerchant gem, which includes multiple gateway implementations, amongst which is Paypal's IPN.

HTH

Nazar
  • 1,499
  • 12
  • 24
  • Do you know if PayPal Payments Standard is supported for all countries? According to the documentation of Active Merchant these are the supported PayPal Gateways: PayPal Express Checkout - US, CA, SG, AU PayPal Payflow Pro - US, CA, SG, AU PayPal Website Payments Pro (UK) - UK PayPal Website Payments Pro (CA) - CA PayPal Express Checkout - US PayPal Website Payments Pro (US) - US – joscas Jan 14 '13 at 12:53
  • @joscas, sorry no. I've used AM's PayPal IPN in both the UK and US without issue. Sorry I couldn't be of further help. – Nazar Jan 14 '13 at 13:58
0

you can just do this to get ipn details. result will show you verified or not. you can get all details from body

post '/english/ipn' do

url = "https://sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate&#{@query}"

body = request.body.string

result = RestClient.post url, body

end

smenon
  • 119
  • 2
  • 3
0

There are a few PayPal gems, and at least one of them (paypal-sdk-rest) includes the PayPal::SDK::Core::API::IPN.valid? method.

Here's how to use it:

class YourController < ApplicationController

  skip_before_action :verify_authenticity_token, only: :your_action

  def your_action
    verified = PayPal::SDK::Core::API::IPN.valid?(request.raw_post)

    if verified
      # Verification passed, do something useful here.
      render nothing: true, status: :ok
    else
      # Verification failed!
      render nothing: true, status: :unprocessable_entity
    end
  end

end
Eliot Sykes
  • 9,616
  • 6
  • 50
  • 64