====== 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