A Developer's Guide To Working With PayPal

in secure code, ruby

PayPal provides two main options for integration into your online store. In this short guide I'll describe both and show how and when to use each.

How It Works

Payment system providers will typically have two options for integrating with online stores:

  1. The first is to use an on-demand payment page, and then notify back to the store via IPN when the purchase was completed.

  2. The second uses the API to create a specific payment page per-request. With the API a store can query PayPal for payment status and provide the user instant access to the product.

On Demand Payment Pages

As the name implies an on-demand payment page is created dynamically from URL parameters and provides the easiest way to integrate a payments provider.

In PayPal one can use this URL to create a new payment page:

https://www.paypal.com/cgi-bin/webscr?cmd=xclick&add=1&business=ynonperek@gmail.com&itemname=my product&item_number=123456&amount=99.99

Now if you change itemname or amount url parameters you'll get a different page with the selected item name and amount. When the payment ends successfully PayPal will send us a payment notification (called in short IPN).

To integrate PayPal payments to your online store then you should:

  1. Let your user select items
  2. Create a URL of the form described above with the selected description and price
  3. Wait for an IPN that says a user has completed her purchase.

Since we can never be sure the IPN was really originated from PayPal, there's a highly recommended fourth step in this scheme:

  1. Validate IPN

To validate the IPN we send it back to PayPal and they verify its signature.

This following ruby code illustrates an endpoint that receives an IPN and validates it:

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)
    uri = URI.parse('https://www.paypal.com/cgi-bin/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

When working with on-demand payment forms, you'll want to tell the user (in your thank you page) to wait for a confirmation email and then sign in to the store to get her digital goods.

For the full list of the URL parameters visit https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/.

A Dedicated Payment Page

The above solution is the easiest to implement and has the advantage that no API keys are required. Anyone can start collecting money via PayPal. I use it in a system that allows anyone to open their own digital store. Users just need to type in their PayPal account email address and they can start selling immediately.

But there's still one problem with that solution: Waiting for the IPN. Usually IPNs are received within the next 1-2 minutes, but in the worst case I've seen them arrive even 10-15 minutes after the purchase. Remember your users won't be able to access their digital goods until after the IPN is received.

To work around that limitation PayPal offers an API endpoint that can let you know immediately when purchases are made, so you can give instant access to your users.

The way to achive this is to access an API endpoint and create a dedicated payment page for a specific amount and items. Then from your site's "thank you" page you can connect PayPal and query about that payment request.

The next snippet illustrates how we'd open a payment request using PayPal API:

require 'paypal-sdk-rest'
include PayPal::SDK::REST

PayPal::SDK::REST.set_config(
  :mode => "sandbox", # "sandbox" or "live"
  :client_id => "EBWKjlELKMYqRNQ6sYvFo64FtaRLRR5BdHEESmha49TM",
  :client_secret => "EO422dn3gQLgDbuwqTjzrFgFtaRLRR5BdHEESmha49TM")

# Build Payment object
@payment = Payment.new({
  :intent =>  "sale",
  :payer =>  {
    :payment_method =>  "paypal" },
  :redirect_urls => {
    :return_url => "http://localhost:3000/payment/execute",
    :cancel_url => "http://localhost:3000/" },
  :transactions =>  [{
    :item_list => {
      :items => [{
        :name => "item",
        :sku => "item",
        :price => "5",
        :currency => "USD",
        :quantity => 1 }]},
    :amount =>  {
      :total =>  "5",
      :currency =>  "USD" },
    :description =>  "This is the payment transaction description." }]})

if @payment.create
  @payment.id     # Payment Id
else
  @payment.error  # Error Hash
end

Note that this code sends a request directly from your server to PayPal, and all purchase details are sent directly in the payment request call. The payment page is thus not affected by URL parameters.

After a user pays she is directed back to our site to show a nice "Thank You" page. In that thank you endpoint we can use our API keys to complete the payment:

# Get payment id from query string following redirect
payment = Payment.find(ENV["PAYMENT_ID"])

# Execute payment using payer_id obtained from query string following redirect
if payment.execute( :payer_id => ENV["PAYER_ID"] )  #return true or false
  logger.info "Payment[#{payment.id}] executed successfully"
else
  logger.error payment.error.inspect
end

For the full documentation and code examples in other languages visit https://developer.paypal.com/docs/api/quickstart/payments/.

When To Use Each

Use on-demand payment pages when you don't mind waiting for an IPN, or you want to collect money for others and may not have their PayPal API keys.

Use dedicated payment pages when you have the seller API keys and prefer to receive instant feedback on the purchase.

Comments