Aug
11
Paypal on Rails, gotchas and tricks
With the help of Activemerchant, integrate Paypal into Rails is really easy, yet there are some gotchas you should be aware of.
- The original ‘_ext-enter’ for ‘cmd’ is deprecated
- If you are to submit multiple line items, the ‘cmd’ value must be ‘_cart’, then you can use item_name_x/quantity_x/amount_x as the line items details
- The built in helper in Activemerchant do not provide the multiple line mapping, I’ved add one myself, the line_items method.
then you can use it in your view like- service.line_items line_items
- The shipping cost to whole cart should be set in ‘handling_cart’, instead of ‘shipping’ as suggested in Paypal’s document, this is really bad.
See the comments you can understand how the customized helper works
# We changed this paypal helper provided by activemerchant because: # # 1) change the initializer to meet our need, for example, # to submit mulitple items, the 'cmd' value should be '_cart', # see here: https://www.x.com/thread/43442 # and here: https://www.x.com/docs/DOC-1340 # the original '_ext-enter' for 'cmd' is deprecated # # 2) add the line_items method to prepare details for each line item # # 3) accroding to https://www.x.com/thread/39507 # if you want to pass shpping cost for the whole cart, # you should set up the 'handling_cart' instead of 'shipping' as documented # mapping :shipping, 'handling_cart' module ActiveMerchant #:nodoc: module Billing #:nodoc: module Integrations #:nodoc: module Paypal class Helper < ActiveMerchant::Billing::Integrations::Helper def initialize(order, account, options = {}) super # indecate we are using thirdparty shopping cart add_field('cmd', '_cart') add_field('upload', '1') add_field('no_shipping', '1') add_field('no_note', '1') add_field('charset', 'utf-8') add_field('address_override', '0') add_field('bn', application_id.to_s.slice(0,32)) unless application_id.blank? end # pass the shipping cost for whole cart mapping :shipping, 'handling_cart' # mapping header image mapping :cpp_header_image, 'cpp_header_image' # add line item details, note the amount_x # should be price instead of line total def line_items(items = []) items.each_with_index do |line, index| add_field("item_name_#{index+1}", line.item.item_name) add_field("amount_#{index+1}", line.price.value_without_unit) add_field("quantity_#{index+1}", line.quantity) end end end end end end end
Also, you need to create your own payment_service_for( I named it paypal_payment_service_for) to support encrypt data before sending to paypal, because the built in payment_service_for in Activemerchant does not suppor that
require_library_or_gem 'action_pack'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module ActionViewHelper
def paypal_payment_service_for(order, account, options = {}, &proc)
raise ArgumentError, "Missing block" unless block_given?
integration_module = ActiveMerchant::Billing::Integrations.const_get(options.delete(:service).to_s.camelize)
encrypt = options.delete(:encrypt)
result = []
result << form_tag(integration_module.service_url, options.delete(:html) || {})
service_class = integration_module.const_get('Helper')
service = service_class.new(order, account, options)
result << capture(service, &proc)
if encrypt
paypal_params = { :cert_id => cert_id }
service.form_fields.each do |field, value|
paypal_params.merge!(field => value)
end
result << hidden_field_tag(:cmd, "_s_xclick")
result << "\n"
result << hidden_field_tag(:encrypted, encrypt_for_paypal(paypal_params))
else
service.form_fields.each do |field, value|
result << hidden_field_tag(field, value)
end
end
result << ''
result= result.join("\n")
concat(result.respond_to?(:html_safe) ? result.html_safe : result)
nil
end
# encrypt values
def encrypt_for_paypal(values)
unless Settings.payment.paypal.test_mode
app_cert_pem = File.read("#{Rails.root}/config/paypal/paypal-pubcert.pem")
app_key_pem = File.read("#{Rails.root}/config/paypal/paypal-prvkey.pem")
paypal_cert_pem = File.read("#{Rails.root}/config/paypal/paypal-cert.pem")
else
app_cert_pem = File.read("#{Rails.root}/config/paypal/paypal-sandbox-pubcert.pem")
app_key_pem = File.read("#{Rails.root}/config/paypal/paypal-sandbox-prvkey.pem")
paypal_cert_pem = File.read("#{Rails.root}/config/paypal/paypal-sandbox-cert.pem")
end
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
def cert_id
Settings.payment.paypal.certificate_id
end
end
end
end
end