Table of Contents

ActiveMerchant

ActiveMerchant is a plugin that interfaces with payment gateways. I'm using it to connect to paypal.com, so all information presented here is based on that premise. It's easy to use, but there are a few details to be aware of.

Sources:

Testing

ActiveMerchant operates in a special test mode. Configure your Rails app accordingly:

unless RAILS_ENV == 'production'
  PAYPAL_ACCOUNT = 'seller@example.com'
  ActiveMerchant::Billing::Base.mode = :test 
else
  PAYPAL_ACCOUNT = 'paypalaccount@example.com'
end

Put this code in your config/environment.rb file.

You're now ready to follow the instructions from the sites mentioned above (under Sources). First, however, we're going to make a small modification to ActiveMerchant. By default, ActiveMerchant tries to communicate with paypal through ssl. In Paypal's sandbox however, ssl only seems to work through port 80 (instead of the default 443 ssl port). Let's adjust ActiveMerchant accordingly:

In the file vendor/plugins/activemerchant/lib/active_merchant/lib/posts_data.rb, look for the method ssl_post. Now change the method definition to look like this:

    def ssl_post(url, data, headers = {})
      uri   = URI.parse(url)

      mode = ActiveMerchant::Billing::Base.mode
      case mode                  
      when :test
        port = 80 # Apparently paypal uses port 80 for testing purposes
      else
        port = uri.port
      end
      
      #http = Net::HTTP.new(uri.host, uri.port)
      http = Net::HTTP.new(uri.host, port)
      ...(etc.)...
    end

Here, we change the http port to 80 if we're in test mode.

We can take this one step further: why not communicate in plain text, while we're testing? In the same file, add this method:

    def plain_post(url, data, headers = {})
      uri   = URI.parse(url)      
      http = Net::HTTP.new(uri.host, 80)
      http.post(uri.request_uri, data, headers).body   
    end

This is just a trimmed down post method. If you want to, you can copy the error handling over from def ssl_post. Now, move to vendor/plugins/activemerchant/lib/active_merchant/billing/integrations/paypal/notification.rb. In this file, modify the acknowledge method:

  def acknowledge(method = 'ssl_post')
    payload =  raw            
    response = self.send(method, Paypal.service_url + '?cmd=_notify-validate', payload,
      'Content-Length' => "#{payload.size}",
      'User-Agent'     => "Active Merchant -- http://activemerchant.org")
              
    raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response)  
    response == "VERIFIED"
  end

Here, we call ssl_post by default (so ActiveMerchant's default behavior is not in any way altered). But if you call the acknowledge method in your payment_controller, you can now supply the argument plain_post if you're in test mode and don't want to use ssl at all:

  def notify    
    notification = Paypal::Notification.new(request.raw_post)   

    if notification.acknowledge('plain_post')
      ... (etc.) ...
  end

Exposing IPN Variables

If you use Paypal's Instant Payment Notification (IPN) service, Paypal will keep you informed about the details of the transaction by posting the IPN variables to a url on your server. ActiveMerchant exposes these variables for you, so you can do stuff like:

      transaction = Transaction.new(:status => notification.status,
                                    :order_id => order.id,
                                    :user_id => game.user_id,
                                    :invoice => notification.invoice.to_s,
                                    :currency_code => notification.currency.to_s,
                                    :amount => notification.amount.to_s,
                                    :invoice_date => DateTime.now,
                                    :raw_paypal_data => request.raw_post)

The IPN variables are stored in an object of class Notification. By default, the custom field (which you can use to store your own value) is mapped to item_id because PayPal doesn‘t return item_number in dispute notifications. That's great, but what if we really need to send along our own custom variable? Here's a fix, applied in ActiveMerchant::Billing::Integrations::Notification:

  # Expose the custom variable separately (added by Onno Schuit, o.schuit residing_at_or_around solin.nl)
  def custom
    params['custom']
  end