#143
Jan 05, 2009

PayPal Security

This episode shows how to encrypt the variables passed to PayPal and verify the authenticity of the payment notifications (IPN).
Download (22.9 MB, 11:39)
alternative download for iPod & Apple TV (16.8 MB, 11:39)

Resources

mkdir certs
cd certs
openssl genrsa -out app_key.pem 1024
openssl req -new -key app_key.pem -x509 -days 365 -out app_cert.pem
mv ~/Downloads/paypal_cert_pem.txt paypal_cert.pem
script/generate nifty_config
# models/cart.rb
def paypal_encrypted(return_url, notify_url)
  values = {
    :business => APP_CONFIG[:paypal_email],
    :cmd => '_cart',
    :upload => 1,
    :return => return_url,
    :invoice => id,
    :notify_url => notify_url,
    :cert_id => APP_CONFIG[:paypal_cert_id]
  }
  line_items.each_with_index do |item, index|
    values.merge!({
      "amount_#{index+1}" => item.unit_price,
      "item_name_#{index+1}" => item.product.name,
      "item_number_#{index+1}" => item.id,
      "quantity_#{index+1}" => item.quantity
    })
  end
  encrypt_for_paypal(values)
end

PAYPAL_CERT_PEM = File.read("#{Rails.root}/certs/paypal_cert.pem")
APP_CERT_PEM = File.read("#{Rails.root}/certs/app_cert.pem")
APP_KEY_PEM = File.read("#{Rails.root}/certs/app_key.pem")

def encrypt_for_paypal(values)
  signed = OpenSSL::PKCS7::sign(OpenSSL::X509::Certificate.new(APP_CERT_PEM), OpenSSL::PKey::RSA.new(APP_KEY_PEM, ''), values.map { |k, v| "#{k}=#{v}" }.join("\n"), [], OpenSSL::PKCS7::BINARY)
  OpenSSL::PKCS7::encrypt([OpenSSL::X509::Certificate.new(PAYPAL_CERT_PEM)], signed.to_der, OpenSSL::Cipher::Cipher::new("DES3"), OpenSSL::PKCS7::BINARY).to_s.gsub("\n", "")
end

# models/payment_notification.rb
def mark_cart_as_purchased
  if status == "Completed" && params[:secret] == APP_CONFIG[:paypal_secret] &&
      params[:receiver_email] == APP_CONFIG[:paypal_email] &&
      params[:mc_gross] == cart.total_price.to_s && params[:mc_currency] == "USD"
    cart.update_attribute(:purchased_at, Time.now)
  end
end
<!-- carts/show.html.erb -->
<% form_tag APP_CONFIG[:paypal_url] do %>
  <%= hidden_field_tag :cmd, "_s-xclick" %>
  <%= hidden_field_tag :encrypted, @cart.paypal_encrypted(products_url, payment_notifications_url(:secret => APP_CONFIG[:paypal_secret])) %>
  <p><%= submit_tag "Checkout" %></p>
<% end %>
# app_config.yml
development:
  paypal_email: seller_1229899173_biz@railscasts.com
  paypal_secret: foobar
  paypal_cert_id: WKKX2FSCFDB8C
  paypal_url: "https://www.sandbox.paypal.com/cgi-bin/webscr"

test:
  paypal_email: test@example.com
  paypal_secret: testsecret
  paypal_cert_id: 123456789
  paypal_url: testpaypalurl

production:
  paypal_email: foo@example.com
  paypal_secret: supersecret
  paypal_cert_id: KLHZSK326HEDS
  paypal_url: "https://www.paypal.com/cgi-bin/webscr"

RSS Feed for Episode Comments 30 comments

1. Jamie Jan 05, 2009 at 01:55

I don't know what the Rails community would be without you, Ryan. Ughf, what a scary thought..


2. Swami Atma Jan 05, 2009 at 02:15

Amen to that. Thanks Ryan. Following you since the start.


3. Coops Jan 05, 2009 at 02:25

Awesome mate... can't wait for the Merchant stuff and hopefully how to manage recurring payments?? please! maybe even automatic invoicing? awww gone to far!


4. Igor Jan 05, 2009 at 02:38

It looks difficult,
anyway thanks,
and waiting for Merchant railscast!


5. wil Jan 05, 2009 at 06:04

I agree with guys above. I wouldn't have started programming in rails if it wasn't for your video casts and your help in forums.

This is really great Ryan. I am also waiting for Merchant railscasts as well.

Thanks you very much!


6. UniversityTutor.com Jan 05, 2009 at 09:01

Thanks Ryan great cast! As someone mentioned above I would really love to see an episode coming up that handles recurring payments/subscriptions with active merchant.

Have heard Trust Commerce has a good service for this? Not sure though.

Many thanks,
Brian
www.UniversityTutor.com


7. Steve Q Jan 05, 2009 at 09:10

Thanks for this very helpful Railscast.

Anyone availing of a paypal subscription instead of a cart should make sure to have a hidden form 'cmd' field with value of '_s-xclick', and an encrypted 'cmd' field with value '_xclick-subscriptions'.

Othewise you'll face the dreaded "We cannot process this transaction because there is a problem with the PayPal email address supplied by the seller."

See this thread for help: http://www.pdncommunity.com/pdn/board/message?board.id=ewp&thread.id=1033

Steve Q


8. Bryce Jan 05, 2009 at 10:34

Ryan,

Thanks for the great series! It has really helped me to understand Paypal processing much better.

Following your screen cast this morning, I run into a Paypal error that reads "There was a problem with the decryption of your secure order. Please contact your merchant."

The form looks right and I followed your command line entries to the letter. Is there an OpenSSL dependency that's not included in Rails or a certain version requirement? Also, do you know of a way to further debug errors in Paypal?

Thanks again for your great screencasts!

Bryce


9. fguillen Jan 05, 2009 at 15:30

Dear Ryan, thanks a lot for your work it is really amazing.

I am still trying to digest all the information on this number.

The first question I have, maybe it is a very stupid question, is how I should management all this secret information on a project that suppose to be open-source?

The repository will be very uncompleted with all these file-keys, secrets, and private information.

The deployment will be not as simple as an a normal project.

Maybe it will be just as the database.yml .. I don't know maybe I just thinking on loud.

Thanks again for your job :)

f.


10. Edgar G. Jan 06, 2009 at 05:04

Wow, spammers are starting to get good, they can post comments that are almost related to the website's topic.


11. Igor Jan 06, 2009 at 05:18

@Edgar
thanks to Ryan, soon they will know Rails better then we are
<B-)


12. Michael Jan 06, 2009 at 06:20

If the PaymentNotification.create method shall be secure you rely on a ssl/https-connection, right? Or is there another secure way where I don't have the https-overhead for my webserver? Or did I missed something?


13. Roob Jan 07, 2009 at 08:55

Ryan, thanks for this one. This is very useful, not only for Rails developers, because the Paypal transaction processes are walked through in detail!


14. Cameron Jan 10, 2009 at 09:34

Totally awesome. Railscasts is becoming a seriously awesome resource. I Hope they keep on going!


15. logansbro Jan 11, 2009 at 07:51

has anyone else had problems with the Paypal Sandbox IPN? It never seems to work (reliably) for me. It seems like I get a ping from it about 20% of the time.


16. Carl Jan 11, 2009 at 20:19

@Logansbro,

I haven't used Paypal, but I did create an app using Authorize.net a little bit ago to do reoccurring billing and I wouldn't be too surprised to find out the sandbox wasn't perfectly reliable since it is just for testing. Obviously when you are dealing with money you have to be careful, and that made me really nervous. I turned my app live and tested it over a period of time with my own credit cards (and reduced amounts) to make absolutely sure. It worked perfectly and the way I expected. The only thing I hate is the fact these places all seem to nickel and dime you to death, rather than telling you what the fees (all of them I mean) will really be up front. They aren't all that large, but every time I turned around there seemed to be another one.


17. Thomas Jan 16, 2009 at 12:04

Is it okay that paypal send back data using plain text? Shouldn't the return URL be https://mysite.com/payment_notification ?


18. martin Jan 18, 2009 at 19:21

Great screencast! Many thanks. I'm still at loss regarding the SSL/HTTPS IPN callback from Paypal. Can't figure out how to get it right. After all /payment_notification is plain unencrypted HTTP.. Help? Anybody? THx!


19. ariel Jan 26, 2009 at 05:51

Nice!
But i have a big question, when paypal returns to the return_url (return to the store) comes with a big url with a lot of variables, how could i make that invisible???


20. ariel Feb 02, 2009 at 12:58

i fix the issue with the return_url, to put the method on the side of paypal, just in the return_url add the field rm with value 2 and this will convert the form of return to the store xxxxx to method POST. if you put value 1 will be GET.


21. Gavin Mar 18, 2009 at 05:47

Hey - quick question about the SSL cert

I noticed that in this tutorial, the SSL cert is only valid for 365 days.

What happens after the 365? Does PayPal send a reminder or do transactions fail silently?

What's the longest period a certificate can be valid for?

Thanks


22. Nathan Fritz Mar 19, 2009 at 06:40

Awesome. I tried to figure this out several months back and hit a brick wall. Thanks!


23. Alexey Poimtsev Apr 21, 2009 at 03:35

Ryan, regarding app_config - plz have a look at http://github.com/eugenebolshakov/app_config/tree/master - very useful plugin :)


24. Tom Jun 26, 2009 at 01:56

Ryan, as always, an extremely helpful railscast. A couple of small points.

The paypal public key is different for the sandbox than it is for the production site. So you need to download them separately and name them differently (if you are going to store both in the same folder). I then needed to add another entry to the app_config.yml file called paypal_cert_name and use it when loading the pem files.

Speaking of app_config. I found it helpful to add an "all" section at the top of the file, and modify the load_app_config.rb file to merge the "all" with the environment specific settings. (i.e APP_CONFIG = YAML.load(raw_config)["all"].symbolize_keys.merge(YAML.load(raw_config)[RAILS_ENV].symbolize_keys))
This allows me to have some settings which span all environments, and can be overwritten in any of them if needed.

Once again, thank you for your great contribution to improving life for rails developers.


25. Nicolas Aug 29, 2009 at 12:18

Hello Ryan,

First, thanks for all the webcasts.

I am trying to implement paypal and I followed your previous webscast and it is working well.
Note that I did not do a link to paypal in the form but I integrated a
redirect_to(@order.paypal(r_url,not_url))
in my controller.
I was wondering if I needed to encrypt as the url was built in my controller?

Thanks


26. sakizimu Nov 29, 2009 at 13:24

I think Second Life should be considered Commercial/RW, at least you must pay something even to upload a file in SL.. While in Youtube it is free….


27. Tex Dec 10, 2009 at 08:02

DES3 algorithm is not supported under jruby_openssl (v. 0.6) so I cannot use PayPal transactions...

Do you know if I can use a different cipher algorithm under jruby ?


28. artemave Dec 17, 2009 at 07:11

Before considering PayPal, consider this:

http://blog.apparentsoft.com/business/124/is-paypal-good-for-your-microisv-business-a-short-paypal-horror-story/


29. javon Jan 25, 2010 at 22:40

Good day!


30. jamesw Feb 03, 2010 at 15:24

I've just been having a play with PayPal and your RailsCasts are as usual extremely good and a massive help. Thankyou.

I would like to ask if it is feasable to use ActiveResource to communicate with sites like this? The new api's in PayPal X certainly seem quite happy to return nicely formatted XML.

If it is then it would be great to see a Railscast on this particularly as all the documentation deals with communicating with other Rails sites.


31. Gustavo M Feb 26, 2010 at 11:56

Hello,

First, thanks for posting this video it has helped me much.

However, I am stuck at the point where I can´t seem to get the APP_CONFIG[:paypal_secret] to get returned to the application and can´t figure out why.

I downloaded the source code and tested the app... but the problem remains.. I cant validate the transaction with the secret.

Hope you can help me.

Thanks.

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source