====== 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: * [[http://activemerchant.rubyforge.org/|ActiveMerchant API]] * [[https://www.paypal.com/IntegrationCenter/ic_ipn-pdt-variable-reference.html|Paypal IPN variables]] * [[http://www.fortytwo.gr/blog/14/Using-Paypal-with-Rails|Using Paypal with Rails]] * [[http://opensoul.org/2006/9/16/paypal-ipn-in-rails-with-active-merchant|Paypal IPN in Rails with Active Merchant]] ===== 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