lib/offsite_payments/integrations/pag_seguro.rb
# encoding: utf-8
module OffsitePayments #:nodoc:
module Integrations #:nodoc:
module PagSeguro
mattr_accessor :service_production_url
self.service_production_url = 'https://pagseguro.uol.com.br/v2/checkout/payment.html'
mattr_accessor :service_test_url
self.service_test_url = 'https://sandbox.pagseguro.uol.com.br/v2/checkout/payment.html'
mattr_accessor :invoicing_production_url
self.invoicing_production_url = 'https://ws.pagseguro.uol.com.br/v2/checkout/'
mattr_accessor :invoicing_test_url
self.invoicing_test_url = 'https://ws.sandbox.pagseguro.uol.com.br/v2/checkout/'
mattr_accessor :notification_production_url
self.notification_production_url = 'https://ws.pagseguro.uol.com.br/v2/transactions/notifications/'
mattr_accessor :notification_test_url
self.notification_test_url = 'https://ws.sandbox.pagseguro.uol.com.br/v2/transactions/notifications/'
def self.service_url
test? ? service_test_url : service_production_url
end
def self.invoicing_url
test? ? invoicing_test_url : invoicing_production_url
end
def self.notification_url
test? ? notification_test_url : notification_production_url
end
def self.notification(query_string, options = {})
Notification.new(query_string, options)
end
def self.return(query_string, options = {})
Return.new(query_string, options)
end
def self.test?
OffsitePayments.mode == :test
end
class Helper < OffsitePayments::Helper
def initialize(order_id, account, options)
super
@account = account
add_field('itemAmount1', sprintf("%0.02f", options[:amount]))
add_field('itemId1', '1')
add_field('itemQuantity1', '1')
add_field('shippingType', '3')
add_field('currency', 'BRL')
end
mapping :account, 'email'
mapping :credential2, 'token'
mapping :order, 'reference'
mapping :notify_url, 'notificationURL'
mapping :return_url, 'redirectURL'
mapping :description, 'itemDescription1'
def shipping_address(params = {})
add_field('shippingAddressCity', params[:city].slice(0, 60)) if params[:city]
add_field('shippingAddressStreet', params[:address1].slice(0, 80)) if params[:address1]
add_field('shippingAddressComplement', params[:address2].slice(0, 40)) if params[:address2]
add_field('shippingAddressState', params[:state])
add_field('shippingAddressPostalCode', params[:zip].delete("^0-9").slice(0, 8)) if params[:zip]
end
def form_fields
invoice_id = fetch_token
{"code" => invoice_id}
end
def shipping(value)
add_field("shippingCost", sprintf("%0.02f", value))
end
def customer(params = {})
full_name = remove_excessive_whitespace("#{params[:first_name]} #{params[:last_name]}")
if phone = area_code_and_number(params[:phone])
add_field("senderAreaCode", phone[0])
add_field("senderPhone", phone[1])
end
add_field("senderEmail", params[:email])
add_field('senderName', full_name)
end
def fetch_token
uri = URI.parse(PagSeguro.invoicing_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request.content_type = "application/x-www-form-urlencoded"
request.set_form_data @fields
response = http.request(request)
xml = Nokogiri::XML.parse(response.body)
check_for_errors(response, xml)
extract_token(xml)
rescue Timeout::Error, Errno::ECONNRESET, Errno::ETIMEDOUT => e
raise ActionViewHelperError, "Erro ao conectar-se ao PagSeguro. Por favor, tente novamente."
end
def area_code_and_number(phone)
return if phone.nil?
phone.gsub!(/[^\d]/, '')
ddd = phone.slice(0..1)
number = phone.slice(2..12)
[ddd, number]
end
def check_for_errors(response, xml)
return if response.code == "200"
case response.code
when "400"
raise ActionViewHelperError, humanize_errors(xml)
when "401"
raise ActionViewHelperError, "Token do PagSeguro inválido."
else
raise ActiveUtils::ResponseError, response
end
end
def extract_token(xml)
xml.css("code").text
end
def humanize_errors(xml)
# reference: https://pagseguro.uol.com.br/v2/guia-de-integracao/codigos-de-erro.html
xml.css("errors").children.map do |error|
case error.css('code').text
when "11013"
"Código de área inválido"
when "11014"
"Número de telefone inválido. Formato esperado: (DD) XXXX-XXXX"
when "11017"
"Código postal (CEP) inválido."
else
error.css('message').text
end
end.join(", ")
end
def remove_excessive_whitespace(text)
text.gsub(/\s{2,}/, ' ').strip
end
end
class Notification < OffsitePayments::Notification
class NotificationError < StandardError; end
def initialize(post, options = {})
@acknowledge = true
notify_code = parse_http_query(post)["notificationCode"]
email = options[:credential1]
token = options[:credential2]
uri = URI.join(PagSeguro.notification_url, notify_code)
parse_xml(web_get(uri, email: email, token: token))
rescue NotificationError
@acknowledge = false
end
def complete?
status == "Completed"
end
def item_id
params["transaction"]["reference"]
end
def transaction_id
params["transaction"]["code"]
end
def received_at
params["transaction"]["date"]
end
def payer_email
params["sender"]["email"]
end
def gross
params["transaction"]["grossAmount"]
end
def currency
"BRL"
end
def payment_method_type
params["transaction"]["paymentMethod"]["type"]
end
def payment_method_code
params["transaction"]["paymentMethod"]["code"]
end
def status
case params["transaction"]["status"]
when "1", "2"
"Pending"
when "3"
"Completed"
when "4"
"Available"
when "5"
"Dispute"
when "6"
"Reversed"
when "7"
"Failed"
end
end
def acknowledge
@acknowledge
end
private
def web_get(uri, params)
uri.query = URI.encode_www_form(params)
response = Net::HTTP.get_response(uri)
raise NotificationError if response.code.to_i > 200
response.body
end
# Take the posted data and move the relevant data into a hash
def parse_xml(post)
@params = Hash.from_xml(post)
end
def parse_http_query(post)
@raw = post
params = {}
for line in post.split('&')
key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
params[key] = value
end
params
end
end
end
end
end