├── spec ├── support │ ├── log │ │ └── .gitkeep │ ├── tmp │ │ ├── .gitkeep │ │ └── pagseguro-test.yml │ ├── app │ │ ├── views │ │ │ ├── session │ │ │ │ └── new.erb │ │ │ └── dashboard │ │ │ │ └── index.erb │ │ ├── models │ │ │ ├── account.rb │ │ │ └── user.rb │ │ └── controllers │ │ │ └── application_controller.rb │ ├── config │ │ ├── database.yml │ │ ├── routes.rb │ │ ├── pagseguro.yml │ │ └── boot.rb │ ├── pagseguro-test.yml │ └── matcher.rb ├── spec_helper.rb ├── fixtures │ └── notification.yml ├── controllers │ └── developer_controller_spec.rb ├── pagseguro │ ├── faker_spec.rb │ ├── pagseguro_spec.rb │ ├── order_spec.rb │ ├── rake_spec.rb │ └── notification_spec.rb └── helpers │ └── helper_spec.rb ├── .rspec ├── Gemfile ├── .gitignore ├── lib ├── pagseguro │ ├── engine.rb │ ├── version.rb │ ├── routes.rb │ ├── helper.rb │ ├── utils.rb │ ├── action_controller.rb │ ├── generator.rb │ ├── railtie.rb │ ├── developer_controller.rb │ ├── base.rb │ ├── order.rb │ ├── rake.rb │ ├── notification.rb │ └── faker.rb ├── tasks │ └── pagseguro.rake └── pagseguro.rb ├── Rakefile ├── templates └── config.yml ├── pagseguro.gemspec ├── app └── views │ └── pagseguro │ └── _pagseguro_form.html.erb ├── Gemfile.lock └── README.markdown /spec/support/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/support/tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color --format documentation -------------------------------------------------------------------------------- /spec/support/app/views/session/new.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /spec/support/app/views/dashboard/index.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | spec/support/tmp/**/* 2 | spec/support/log/*.log 3 | pkg -------------------------------------------------------------------------------- /spec/support/app/models/account.rb: -------------------------------------------------------------------------------- 1 | class Account < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/support/config/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: sqlite3 3 | database: ":memory:" -------------------------------------------------------------------------------- /spec/support/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | authentication 3 | end 4 | -------------------------------------------------------------------------------- /lib/pagseguro/engine.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro #:nodoc: 2 | class Engine < ::Rails::Engine #:nodoc: 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /spec/support/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler::GemHelper.install_tasks 3 | 4 | require "rspec/core/rake_task" 5 | RSpec::Core::RakeTask.new 6 | -------------------------------------------------------------------------------- /spec/support/config/routes.rb: -------------------------------------------------------------------------------- 1 | PagSeguro::Application.routes.draw do 2 | get "dashboard", :to => "dashboard#index" 3 | get "login", :to => "session#new" 4 | end 5 | -------------------------------------------------------------------------------- /lib/pagseguro/version.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | module Version 3 | MAJOR = 0 4 | MINOR = 1 5 | PATCH = 13 6 | STRING = "#{MAJOR}.#{MINOR}.#{PATCH}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/pagseguro/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get "pagseguro_developer/confirm", :to => "pag_seguro/developer#confirm" 3 | post "pagseguro_developer", :to => "pag_seguro/developer#create" 4 | end 5 | -------------------------------------------------------------------------------- /lib/tasks/pagseguro.rake: -------------------------------------------------------------------------------- 1 | namespace :pagseguro do 2 | desc "Send notification to the URL specified in your config/pagseguro.yml file" 3 | task :notify => :environment do 4 | PagSeguro::Rake.run 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require "rails" 3 | require "fakeweb" 4 | require "pagseguro" 5 | require "support/config/boot" 6 | require "rspec/rails" 7 | require "nokogiri" 8 | require "support/matcher" 9 | 10 | FakeWeb.allow_net_connect = false 11 | -------------------------------------------------------------------------------- /lib/pagseguro/helper.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro::Helper 2 | def pagseguro_form(order, options = {}) 3 | options.reverse_merge!(:submit => "Pagar com PagSeguro") 4 | render :partial => "pagseguro/pagseguro_form", :locals => {:options => options, :order => order} 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/pagseguro/utils.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | module Utils 3 | extend self 4 | 5 | def to_utf8(string) 6 | string.to_s.unpack("C*").pack("U*") 7 | end 8 | 9 | def to_iso8859(string) 10 | string.to_s.unpack("U*").pack("C*") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/config/pagseguro.yml: -------------------------------------------------------------------------------- 1 | development: &development 2 | developer: true 3 | return_to: "/invoices/confirmation" 4 | base: "http://localhost:3000" 5 | email: "john@doe.com" 6 | 7 | test: 8 | <<: *development 9 | 10 | production: 11 | authenticity_token: 9CA8D46AF0C6177CB4C23D76CAF5E4B0 12 | email: user@example.com 13 | -------------------------------------------------------------------------------- /lib/pagseguro/action_controller.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | module ActionController 3 | private 4 | def pagseguro_notification(token = nil, &block) 5 | return unless request.post? 6 | 7 | notification = PagSeguro::Notification.new(params, token) 8 | yield notification if notification.valid? 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/pagseguro/generator.rb: -------------------------------------------------------------------------------- 1 | require "rails/generators/base" 2 | 3 | module PagSeguro 4 | class InstallGenerator < ::Rails::Generators::Base 5 | namespace "pagseguro:install" 6 | source_root File.dirname(__FILE__) + "/../../templates" 7 | 8 | def copy_configuration_file 9 | copy_file "config.yml", "config/pagseguro.yml" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /templates/config.yml: -------------------------------------------------------------------------------- 1 | development: &development 2 | developer: true 3 | base: "http://localhost:3000" 4 | return_to: "/pedido/efetuado" 5 | email: user@example.com 6 | 7 | test: 8 | <<: *development 9 | 10 | production: 11 | authenticity_token: 9CA8D46AF0C6177CB4C23D76CAF5E4B0 12 | email: user@example.com 13 | return_to: "http://exemplo.com.br/pedido/efetuado" 14 | -------------------------------------------------------------------------------- /lib/pagseguro.rb: -------------------------------------------------------------------------------- 1 | require "net/https" 2 | require "uri" 3 | require "time" 4 | require "bigdecimal" 5 | 6 | require "pagseguro/base" 7 | require "pagseguro/engine" 8 | require "pagseguro/faker" 9 | require "pagseguro/rake" 10 | require "pagseguro/railtie" 11 | require "pagseguro/notification" 12 | require "pagseguro/order" 13 | require "pagseguro/action_controller" 14 | require "pagseguro/helper" 15 | require "pagseguro/utils" 16 | -------------------------------------------------------------------------------- /spec/support/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] = File.dirname(__FILE__) + "/../../../Gemfile" 2 | require "bundler" 3 | Bundler.setup 4 | require "rails/all" 5 | Bundler.require(:default) 6 | 7 | module PagSeguro 8 | class Application < Rails::Application 9 | config.root = File.dirname(__FILE__) + "/.." 10 | config.active_support.deprecation = :log 11 | end 12 | end 13 | 14 | PagSeguro::Application.initialize! 15 | -------------------------------------------------------------------------------- /lib/pagseguro/railtie.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | class Railtie < Rails::Railtie 3 | generators do 4 | require "pagseguro/generator" 5 | end 6 | 7 | initializer :add_routing_paths do |app| 8 | if PagSeguro.developer? 9 | app.routes_reloader.paths.unshift(File.dirname(__FILE__) + "/routes.rb") 10 | end 11 | end 12 | 13 | rake_tasks do 14 | load File.dirname(__FILE__) + "/../tasks/pagseguro.rake" 15 | end 16 | 17 | initializer "pagseguro.initialize" do |app| 18 | ::ActionView::Base.send(:include, PagSeguro::Helper) 19 | ::ActionController::Base.send(:include, PagSeguro::ActionController) 20 | end 21 | 22 | config.after_initialize do 23 | require "pagseguro/developer_controller" if PagSeguro.developer? 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/fixtures/notification.yml: -------------------------------------------------------------------------------- 1 | --- 2 | VendedorEmail: vendedor@example.com 3 | TransacaoID: 2339ADE917F744DEBEDA890C826386EG 4 | Referencia: "1" 5 | Extras: "0,00" 6 | TipoFrete: FR 7 | ValorFrete: "0,00" 8 | Anotacao: "Pagamento do carro" 9 | DataTransacao: 26/05/2011 12:17:47 10 | TipoPagamento: Pagamento Online 11 | StatusTransacao: Aprovado 12 | CliNome: "Jos\xC3\xA9 da Silva" 13 | CliEmail: comprador@exemple.com 14 | CliEndereco: RUA DE CIMA 15 | CliNumero: "1145" 16 | CliComplemento: apto 1 17 | CliBairro: "Pequen\xC3\xB3polis" 18 | CliCidade: SAO PAULO 19 | CliEstado: SP 20 | CliCEP: "04253000" 21 | CliTelefone: 11 75379467 22 | NumItens: "1" 23 | Parcelas: "1" 24 | ProdID_1: "9" 25 | ProdDescricao_1: Carro Zero Km 26 | ProdValor_1: "15,00" 27 | ProdQuantidade_1: "1" 28 | ProdFrete_1: "0,00" 29 | ProdExtras_1: "0,00" -------------------------------------------------------------------------------- /spec/support/pagseguro-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ABC: !map:HashWithIndifferentAccess 3 | commit: Pagar com PagSeguro 4 | email_cobranca: user@example.com 5 | encoding: utf-8 6 | 7 | item_descr_1: Ruby 1.9 PDF 8 | item_frete_1: "0" 9 | item_id_1: "1" 10 | item_peso_1: "0" 11 | item_quant_1: "1" 12 | item_valor_1: "900" 13 | 14 | item_descr_2: Ruby 1.9 Screencast 15 | item_frete_2: "0" 16 | item_id_2: "2" 17 | item_peso_2: "0" 18 | item_quant_2: "1" 19 | item_valor_2: "1250" 20 | 21 | item_descr_3: Ruby T-Shirt 22 | item_frete_3: "250" 23 | item_id_3: "3" 24 | item_peso_3: "300" 25 | item_quant_3: "2" 26 | item_valor_3: "1989" 27 | 28 | item_descr_4: Ruby Mug 29 | item_id_4: "4" 30 | item_peso_4: "100" 31 | item_quant_4: "1" 32 | item_valor_4: "1599" 33 | 34 | moeda: BRL 35 | ref_transacao: ABC 36 | tipo: CP 37 | -------------------------------------------------------------------------------- /spec/support/tmp/pagseguro-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ABC: !map:HashWithIndifferentAccess 3 | commit: Pagar com PagSeguro 4 | email_cobranca: user@example.com 5 | encoding: utf-8 6 | 7 | item_descr_1: Ruby 1.9 PDF 8 | item_frete_1: "0" 9 | item_id_1: "1" 10 | item_peso_1: "0" 11 | item_quant_1: "1" 12 | item_valor_1: "900" 13 | 14 | item_descr_2: Ruby 1.9 Screencast 15 | item_frete_2: "0" 16 | item_id_2: "2" 17 | item_peso_2: "0" 18 | item_quant_2: "1" 19 | item_valor_2: "1250" 20 | 21 | item_descr_3: Ruby T-Shirt 22 | item_frete_3: "250" 23 | item_id_3: "3" 24 | item_peso_3: "300" 25 | item_quant_3: "2" 26 | item_valor_3: "1989" 27 | 28 | item_descr_4: Ruby Mug 29 | item_id_4: "4" 30 | item_peso_4: "100" 31 | item_quant_4: "1" 32 | item_valor_4: "1599" 33 | 34 | moeda: BRL 35 | ref_transacao: ABC 36 | tipo: CP 37 | -------------------------------------------------------------------------------- /spec/controllers/developer_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PagSeguro::DeveloperController do 4 | let(:file_path) { PagSeguro::DeveloperController::PAGSEGURO_ORDERS_FILE } 5 | let(:orders) { YAML.load_file(file_path) } 6 | 7 | before do 8 | File.unlink(file_path) if File.exist?(file_path) 9 | end 10 | 11 | it "should create file when it doesn't exist" do 12 | post :create 13 | File.should be_file(file_path) 14 | end 15 | 16 | it "should save sent params" do 17 | post :create, :email_cobranca => "john@doe.com", :ref_transacao => "I1001" 18 | orders["I1001"]["email_cobranca"].should == "john@doe.com" 19 | orders["I1001"]["ref_transacao"].should == "I1001" 20 | end 21 | 22 | it "should redirect to the return url" do 23 | post :create, :ref_transacao => "I1001" 24 | response.should redirect_to("/invoices/confirmation") 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/pagseguro/developer_controller.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | class DeveloperController < ::ActionController::Base 3 | skip_before_filter :verify_authenticity_token 4 | PAGSEGURO_ORDERS_FILE = File.join(Rails.root, "tmp", "pagseguro-#{Rails.env}.yml") 5 | 6 | def create 7 | # create the orders file if doesn't exist 8 | FileUtils.touch(PAGSEGURO_ORDERS_FILE) unless File.exist?(PAGSEGURO_ORDERS_FILE) 9 | 10 | # YAML caveat: if file is empty false is returned; 11 | # we need to set default to an empty hash in this case 12 | orders = YAML.load_file(PAGSEGURO_ORDERS_FILE) || {} 13 | 14 | # add a new order, associating it with the order id 15 | orders[params[:ref_transacao]] = params.except(:controller, :action, :only_path, :authenticity_token) 16 | 17 | # save the file 18 | File.open(PAGSEGURO_ORDERS_FILE, "w+") do |file| 19 | file << orders.to_yaml 20 | end 21 | 22 | # redirect to the configuration url 23 | redirect_to PagSeguro.config["return_to"] 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /pagseguro.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "pagseguro/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "pagseguro" 7 | s.version = PagSeguro::Version::STRING 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Nando Vieira"] 10 | s.email = ["fnando.vieira@gmail.com"] 11 | s.homepage = "http://rubygems.org/gems/pagseguro" 12 | s.summary = "The official PagSeguro library" 13 | s.description = s.summary 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 18 | s.require_paths = ["lib"] 19 | 20 | s.add_development_dependency "rails" , "~> 3.1" 21 | s.add_development_dependency "rake" , "~> 0.9" 22 | s.add_development_dependency "fakeweb" , "~> 1.3" 23 | s.add_development_dependency "rspec-rails" , "~> 2.7" 24 | s.add_development_dependency "nokogiri" , "~> 1.5" 25 | s.add_development_dependency "sqlite3" , "~> 1.3" 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_input do |options| 2 | match do |html| 3 | options.reverse_merge!(:type => "hidden") 4 | 5 | selector = "input" 6 | selector << "[type=#{options[:type]}]" 7 | selector << "[name=#{options[:name]}]" if options[:name] 8 | 9 | input = html.css(selector).first 10 | 11 | if options[:value] 12 | input && input[:value] == options[:value] 13 | else 14 | input != nil 15 | end 16 | end 17 | 18 | failure_message_for_should do |html| 19 | "expected #{html.to_s} to have a field with attributes #{options.inspect}" 20 | end 21 | 22 | failure_message_for_should_not do |html| 23 | "expected #{html.to_s} to have no field with attributes #{options.inspect}" 24 | end 25 | end 26 | 27 | RSpec::Matchers.define :have_attr do |name, value| 28 | match do |html| 29 | html[name] == value 30 | end 31 | 32 | failure_message_for_should do |html| 33 | "expected #{html.to_s} to have a #{name.inspect} with value #{value.inspect}" 34 | end 35 | 36 | description do 37 | "should have attribute #{name.inspect} with value #{value.inspect}" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/views/pagseguro/_pagseguro_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= hidden_field_tag "encoding", "UTF-8" %> 4 | <%= hidden_field_tag "email_cobranca", options.fetch(:email, PagSeguro.config["email"]) %> 5 | <%= hidden_field_tag "tipo", "CP" %> 6 | <%= hidden_field_tag "moeda", "BRL" %> 7 | <%= hidden_field_tag "ref_transacao", order.id %> 8 | <%= hidden_field_tag "tipo_frete", order.shipping_type if order.shipping_type %> 9 | 10 | <% order.products.each_with_index do |product, i| %> 11 | <% i += 1 %> 12 | <%= hidden_field_tag "item_quant_#{i}", product[:quantity] %> 13 | <%= hidden_field_tag "item_id_#{i}", product[:id] %> 14 | <%= hidden_field_tag "item_descr_#{i}", product[:description] %> 15 | <%= hidden_field_tag "item_valor_#{i}", product[:price] %> 16 | <%= hidden_field_tag "item_peso_#{i}", product[:weight].to_i if product[:weight] %> 17 | <%= hidden_field_tag "item_frete_#{i}", product[:shipping].to_i if product[:shipping] %> 18 | <% end %> 19 | 20 | <% order.billing.each do |name, value| %> 21 | <%= hidden_field_tag PagSeguro::Order::BILLING_MAPPING[name.to_sym], value %> 22 | <% end %> 23 | 24 | <%= submit_tag options[:submit] %> 25 |
26 |
27 | -------------------------------------------------------------------------------- /lib/pagseguro/base.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | extend self 3 | 4 | # PagSeguro receives all invoices in this URL. If developer mode is enabled, 5 | # then the URL will be /pagseguro_developer/invoice 6 | GATEWAY_URL = "https://pagseguro.uol.com.br/checkout/checkout.jhtml" 7 | 8 | # Hold the config/pagseguro.yml contents 9 | @@config = nil 10 | 11 | # The path to the configuration file 12 | def config_file 13 | Rails.root.join("config/pagseguro.yml") 14 | end 15 | 16 | # Check if configuration file exists. 17 | def config? 18 | File.exist?(config_file) 19 | end 20 | 21 | # Load configuration file. 22 | def config 23 | raise MissingConfigurationError, "file not found on #{config_file.inspect}" unless config? 24 | 25 | # load file if is not loaded yet 26 | @@config ||= YAML.load_file(config_file) 27 | 28 | # raise an exception if the environment hasn't been set 29 | # or if file is empty 30 | if @@config == false || !@@config[Rails.env] 31 | raise MissingEnvironmentError, ":#{Rails.env} environment not set on #{config_file.inspect}" 32 | end 33 | 34 | # retrieve the environment settings 35 | @@config[Rails.env] 36 | end 37 | 38 | # The gateway URL will point to a local URL is 39 | # app is running in developer mode 40 | def gateway_url 41 | if developer? 42 | "/pagseguro_developer" 43 | else 44 | GATEWAY_URL 45 | end 46 | end 47 | 48 | # Reader for the `developer` configuration 49 | def developer? 50 | config? && config["developer"] == true 51 | end 52 | 53 | class MissingEnvironmentError < StandardError; end 54 | class MissingConfigurationError < StandardError; end 55 | end 56 | -------------------------------------------------------------------------------- /spec/pagseguro/faker_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PagSeguro::Faker do 4 | it "should return city" do 5 | PagSeguro::Faker::CITIES.should include(PagSeguro::Faker.city) 6 | end 7 | 8 | it "should return state" do 9 | PagSeguro::Faker::STATES.should include(PagSeguro::Faker.state) 10 | end 11 | 12 | it "should return street name" do 13 | PagSeguro::Faker::STREET_TYPES.stub :sample => "Alameda" 14 | PagSeguro::Faker::CITIES.stub :sample => "Horizontina" 15 | PagSeguro::Faker.street_name.should == "Alameda Horizontina" 16 | end 17 | 18 | it "should return secondary address" do 19 | PagSeguro::Faker::SECONDARY_ADDRESS.stub :sample => "Apto" 20 | PagSeguro::Faker.stub :rand => 666 21 | PagSeguro::Faker.secondary_address.should == "Apto 666" 22 | end 23 | 24 | it "should return name" do 25 | PagSeguro::Faker::NAMES.should include(PagSeguro::Faker.name) 26 | end 27 | 28 | it "should return surname" do 29 | PagSeguro::Faker::SURNAMES.should include(PagSeguro::Faker.surname) 30 | end 31 | 32 | it "should return full name" do 33 | PagSeguro::Faker.stub :name => "John" 34 | PagSeguro::Faker.stub :surname => "Doe" 35 | PagSeguro::Faker.full_name.should == "John Doe" 36 | end 37 | 38 | it "should return email" do 39 | PagSeguro::Faker.stub :full_name => "John Doe" 40 | PagSeguro::Faker.email.should match(/john.doe@(gmail|yahoo|hotmail|uol|ig|bol)/) 41 | end 42 | 43 | it "should return phone number" do 44 | PagSeguro::Faker.phone_number.should match(/\(\d{2}\) \d{4}-\d{4}/) 45 | end 46 | 47 | it "should return zip code" do 48 | PagSeguro::Faker.zipcode.should match(/\d{5}-\d{3}/) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/pagseguro/pagseguro_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PagSeguro do 4 | describe "configuration" do 5 | before do 6 | @config_file = Rails.root.join("config/pagseguro.yml") 7 | @contents = YAML.load_file(@config_file) 8 | File.stub :exists? => true 9 | YAML.stub :load_file => @contents 10 | 11 | module PagSeguro; @@config = nil; end 12 | end 13 | 14 | it "should raise error if configuration is not found" do 15 | File.should_receive(:exist?).with(@config_file).and_return(false) 16 | expect { PagSeguro.config }.to raise_error(PagSeguro::MissingConfigurationError) 17 | end 18 | 19 | it "should raise error if no environment is set on config file" do 20 | YAML.should_receive(:load_file).with(@config_file).and_return({}) 21 | expect { PagSeguro.config }.to raise_error(PagSeguro::MissingEnvironmentError) 22 | end 23 | 24 | it "should raise error if config file is empty" do 25 | # YAML.load_file return false when file is zero-byte 26 | YAML.should_receive(:load_file).with(@config_file).and_return(false) 27 | expect { PagSeguro.config }.to raise_error(PagSeguro::MissingEnvironmentError) 28 | end 29 | 30 | it "should return local url if developer mode is enabled" do 31 | PagSeguro.should_receive(:developer?).and_return(true) 32 | PagSeguro.gateway_url.should == "/pagseguro_developer" 33 | end 34 | 35 | it "should return real url if developer mode is disabled" do 36 | PagSeguro.should_receive(:developer?).and_return(false) 37 | PagSeguro.gateway_url.should == "https://pagseguro.uol.com.br/checkout/checkout.jhtml" 38 | end 39 | 40 | it "should read configuration developer mode" do 41 | PagSeguro.stub :config => {"developer" => true} 42 | PagSeguro.should be_developer 43 | 44 | PagSeguro.stub :config => {"developer" => false} 45 | PagSeguro.should_not be_developer 46 | end 47 | end 48 | end -------------------------------------------------------------------------------- /spec/pagseguro/order_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PagSeguro::Order do 4 | before do 5 | @order = PagSeguro::Order.new 6 | @product = {:price => 9.90, :description => "Ruby 1.9 PDF", :id => 1} 7 | end 8 | 9 | it "should set order id when instantiating object" do 10 | @order = PagSeguro::Order.new("ABCDEF") 11 | @order.id.should == "ABCDEF" 12 | end 13 | 14 | it "should set order id throught setter" do 15 | @order.id = "ABCDEF" 16 | @order.id.should == "ABCDEF" 17 | end 18 | 19 | it "should reset products" do 20 | @order.products += [1,2,3] 21 | @order.products.should have(3).items 22 | @order.reset! 23 | @order.products.should be_empty 24 | end 25 | 26 | it "should alias add method" do 27 | @order.should_receive(:<<).with(:id => 1) 28 | @order.add :id => 1 29 | end 30 | 31 | it "should add product with default settings" do 32 | @order << @product 33 | @order.products.should have(1).item 34 | 35 | p = @order.products.first 36 | p[:price].should == 990 37 | p[:description].should == "Ruby 1.9 PDF" 38 | p[:id].should == 1 39 | p[:quantity].should == 1 40 | p[:weight].should be_nil 41 | p[:fees].should be_nil 42 | p[:shipping].should be_nil 43 | end 44 | 45 | it "should add product with custom settings" do 46 | @order << @product.merge(:quantity => 3, :shipping => 3.50, :weight => 100, :fees => 1.50) 47 | @order.products.should have(1).item 48 | 49 | p = @order.products.first 50 | p[:price].should == 990 51 | p[:description].should == "Ruby 1.9 PDF" 52 | p[:id].should == 1 53 | p[:quantity].should == 3 54 | p[:weight].should == 100 55 | p[:shipping].should == 350 56 | p[:fees].should == 150 57 | end 58 | 59 | it "should convert amounts to cents" do 60 | @order << @product.merge(:price => 9.99, :shipping => 3.67) 61 | 62 | p = @order.products.first 63 | p[:price].should == 999 64 | p[:shipping].should == 367 65 | end 66 | 67 | specify "bug fix: should convert 1.15 correctly" do 68 | @order << @product.merge(:price => 1.15) 69 | 70 | p = @order.products.first 71 | p[:price].should == 115 72 | end 73 | 74 | it "should convert big decimal to cents" do 75 | @product.merge!(:price => BigDecimal.new("199.00")) 76 | @order << @product 77 | 78 | p = @order.products.first 79 | p[:price].should == 19900 80 | end 81 | 82 | it "should convert weight to grammes" do 83 | @order << @product.merge(:weight => 1.3) 84 | @order.products.first[:weight].should == 1300 85 | end 86 | 87 | it "should respond to billing attribute" do 88 | @order.should respond_to(:billing) 89 | end 90 | 91 | it "should initialize billing attribute" do 92 | @order.billing.should be_instance_of(Hash) 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | pagseguro (0.1.13) 5 | 6 | GEM 7 | remote: http://rubygems.org/ 8 | specs: 9 | actionmailer (3.1.1) 10 | actionpack (= 3.1.1) 11 | mail (~> 2.3.0) 12 | actionpack (3.1.1) 13 | activemodel (= 3.1.1) 14 | activesupport (= 3.1.1) 15 | builder (~> 3.0.0) 16 | erubis (~> 2.7.0) 17 | i18n (~> 0.6) 18 | rack (~> 1.3.2) 19 | rack-cache (~> 1.1) 20 | rack-mount (~> 0.8.2) 21 | rack-test (~> 0.6.1) 22 | sprockets (~> 2.0.2) 23 | activemodel (3.1.1) 24 | activesupport (= 3.1.1) 25 | builder (~> 3.0.0) 26 | i18n (~> 0.6) 27 | activerecord (3.1.1) 28 | activemodel (= 3.1.1) 29 | activesupport (= 3.1.1) 30 | arel (~> 2.2.1) 31 | tzinfo (~> 0.3.29) 32 | activeresource (3.1.1) 33 | activemodel (= 3.1.1) 34 | activesupport (= 3.1.1) 35 | activesupport (3.1.1) 36 | multi_json (~> 1.0) 37 | arel (2.2.1) 38 | builder (3.0.0) 39 | diff-lcs (1.1.3) 40 | erubis (2.7.0) 41 | fakeweb (1.3.0) 42 | hike (1.2.1) 43 | i18n (0.6.0) 44 | json (1.6.1) 45 | mail (2.3.0) 46 | i18n (>= 0.4.0) 47 | mime-types (~> 1.16) 48 | treetop (~> 1.4.8) 49 | mime-types (1.17.2) 50 | multi_json (1.0.3) 51 | nokogiri (1.5.0) 52 | polyglot (0.3.3) 53 | rack (1.3.5) 54 | rack-cache (1.1) 55 | rack (>= 0.4) 56 | rack-mount (0.8.3) 57 | rack (>= 1.0.0) 58 | rack-ssl (1.3.2) 59 | rack 60 | rack-test (0.6.1) 61 | rack (>= 1.0) 62 | rails (3.1.1) 63 | actionmailer (= 3.1.1) 64 | actionpack (= 3.1.1) 65 | activerecord (= 3.1.1) 66 | activeresource (= 3.1.1) 67 | activesupport (= 3.1.1) 68 | bundler (~> 1.0) 69 | railties (= 3.1.1) 70 | railties (3.1.1) 71 | actionpack (= 3.1.1) 72 | activesupport (= 3.1.1) 73 | rack-ssl (~> 1.3.2) 74 | rake (>= 0.8.7) 75 | rdoc (~> 3.4) 76 | thor (~> 0.14.6) 77 | rake (0.9.2.2) 78 | rdoc (3.11) 79 | json (~> 1.4) 80 | rspec (2.7.0) 81 | rspec-core (~> 2.7.0) 82 | rspec-expectations (~> 2.7.0) 83 | rspec-mocks (~> 2.7.0) 84 | rspec-core (2.7.1) 85 | rspec-expectations (2.7.0) 86 | diff-lcs (~> 1.1.2) 87 | rspec-mocks (2.7.0) 88 | rspec-rails (2.7.0) 89 | actionpack (~> 3.0) 90 | activesupport (~> 3.0) 91 | railties (~> 3.0) 92 | rspec (~> 2.7.0) 93 | sprockets (2.0.3) 94 | hike (~> 1.2) 95 | rack (~> 1.0) 96 | tilt (~> 1.1, != 1.3.0) 97 | sqlite3 (1.3.4) 98 | thor (0.14.6) 99 | tilt (1.3.3) 100 | treetop (1.4.10) 101 | polyglot 102 | polyglot (>= 0.3.1) 103 | tzinfo (0.3.31) 104 | 105 | PLATFORMS 106 | ruby 107 | 108 | DEPENDENCIES 109 | fakeweb (~> 1.3) 110 | nokogiri (~> 1.5) 111 | pagseguro! 112 | rails (~> 3.1) 113 | rake (~> 0.9) 114 | rspec-rails (~> 2.7) 115 | sqlite3 (~> 1.3) 116 | -------------------------------------------------------------------------------- /lib/pagseguro/order.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | class Order 3 | # Map all billing attributes that will be added as form inputs. 4 | BILLING_MAPPING = { 5 | :name => "cliente_nome", 6 | :address_zipcode => "cliente_cep", 7 | :address_street => "cliente_end", 8 | :address_number => "cliente_num", 9 | :address_complement => "cliente_compl", 10 | :address_neighbourhood => "cliente_bairro", 11 | :address_city => "cliente_cidade", 12 | :address_state => "cliente_uf", 13 | :address_country => "cliente_pais", 14 | :phone_area_code => "cliente_ddd", 15 | :phone_number => "cliente_tel", 16 | :email => "cliente_email" 17 | } 18 | 19 | # The list of products added to the order 20 | attr_accessor :products 21 | 22 | # The billing info that will be sent to PagSeguro. 23 | attr_accessor :billing 24 | 25 | # Define the shipping type. 26 | # Can be EN (PAC) or SD (Sedex) 27 | attr_accessor :shipping_type 28 | 29 | def initialize(order_id = nil) 30 | reset! 31 | self.id = order_id 32 | self.billing = {} 33 | end 34 | 35 | # Set the order identifier. Should be a unique 36 | # value to identify this order on your own application 37 | def id=(identifier) 38 | @id = identifier 39 | end 40 | 41 | # Get the order identifier 42 | def id 43 | @id 44 | end 45 | 46 | # Remove all products from this order 47 | def reset! 48 | @products = [] 49 | end 50 | 51 | # Add a new product to the PagSeguro order 52 | # The allowed values are: 53 | # - weight (Optional. If float, will be multiplied by 1000g) 54 | # - shipping (Optional. If float, will be multiplied by 100 cents) 55 | # - quantity (Optional. Defaults to 1) 56 | # - price (Required. If float, will be multiplied by 100 cents) 57 | # - description (Required. Identifies the product) 58 | # - id (Required. Should match the product on your database) 59 | # - fees (Optional. If float, will be multiplied by 100 cents) 60 | def <<(options) 61 | options = { 62 | :weight => nil, 63 | :shipping => nil, 64 | :fees => nil, 65 | :quantity => 1 66 | }.merge(options) 67 | 68 | # convert shipping to cents 69 | options[:shipping] = convert_unit(options[:shipping], 100) 70 | 71 | # convert fees to cents 72 | options[:fees] = convert_unit(options[:fees], 100) 73 | 74 | # convert price to cents 75 | options[:price] = convert_unit(options[:price], 100) 76 | 77 | # convert weight to grammes 78 | options[:weight] = convert_unit(options[:weight], 1000) 79 | 80 | products.push(options) 81 | end 82 | 83 | def add(options) 84 | self << options 85 | end 86 | 87 | private 88 | def convert_unit(number, unit) 89 | number = (BigDecimal("#{number}") * unit).to_i unless number.nil? || number.kind_of?(Integer) 90 | number 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/pagseguro/rake.rb: -------------------------------------------------------------------------------- 1 | module PagSeguro 2 | module Rake 3 | extend self 4 | 5 | def run 6 | require "digest/md5" 7 | 8 | env = ENV.inject({}) do |buffer, (name, value)| 9 | value = value.respond_to?(:force_encoding) ? value.dup.force_encoding("UTF-8") : value 10 | buffer.merge(name => value) 11 | end 12 | 13 | # Not running in developer mode? Exit! 14 | unless PagSeguro.developer? 15 | puts "=> [PagSeguro] Can only notify development URLs" 16 | puts "=> [PagSeguro] Double check your config/pagseguro.yml file" 17 | exit 1 18 | end 19 | 20 | # There's no orders file! Exit! 21 | unless File.exist?(PagSeguro::DeveloperController::PAGSEGURO_ORDERS_FILE) 22 | puts "=> [PagSeguro] No orders added. Exiting now!" 23 | exit 1 24 | end 25 | 26 | # Load the orders file 27 | orders = YAML.load_file(PagSeguro::DeveloperController::PAGSEGURO_ORDERS_FILE) 28 | 29 | # Ops! No orders added! Exit! 30 | unless orders 31 | puts "=> [PagSeguro] No invoices created. Exiting now!" 32 | exit 1 33 | end 34 | 35 | # Get the specified order 36 | order = orders[env["ID"]] 37 | 38 | # Not again! No order! Exit! 39 | unless order 40 | puts "=> [PagSeguro] The order #{env['ID'].inspect} could not be found. Exiting now!" 41 | exit 1 42 | end 43 | 44 | # Set the client's info 45 | name = env.fetch("NAME", Faker.name) 46 | email = env.fetch("EMAIL", Faker.email) 47 | 48 | order["CliNome"] = name 49 | order["CliEmail"] = email 50 | order["CliEndereco"] = Faker.street_name 51 | order["CliNumero"] = rand(1000) 52 | order["CliComplemento"] = Faker.secondary_address 53 | order["CliBairro"] = Faker.city 54 | order["CliCidade"] = Faker.city 55 | order["CliCEP"] = Faker.zipcode 56 | order["CliTelefone"] = Faker.phone_number 57 | 58 | # Set the transaction date 59 | order["DataTransacao"] = Time.now.strftime("%d/%m/%Y %H:%M:%S") 60 | 61 | # Replace the order id to the correct name 62 | order["Referencia"] = order.delete("ref_transacao") 63 | 64 | # Count the number of products in this order 65 | order["NumItens"] = order.inject(0) do |count, (key, value)| 66 | count += 1 if key =~ /item_id_/ 67 | count 68 | end 69 | 70 | to_price = proc do |price| 71 | if price.to_s =~ /^(.*?)(.{2})$/ 72 | "#{$1},#{$2}" 73 | else 74 | "0,00" 75 | end 76 | end 77 | 78 | for index in (1..order["NumItens"]) 79 | order["ProdID_#{index}"] = order.delete("item_id_#{index}") 80 | order["ProdDescricao_#{index}"] = order.delete("item_descr_#{index}") 81 | order["ProdValor_#{index}"] = to_price.call(order.delete("item_valor_#{index}")) 82 | order["ProdQuantidade_#{index}"] = order.delete("item_quant_#{index}") 83 | order["ProdFrete_#{index}"] = to_price.call(order.delete("item_frete_#{index}")) 84 | order["ProdExtras_#{index}"] = "0,00" 85 | end 86 | 87 | # Retrieve the specified status or default to :completed 88 | status = env.fetch("STATUS", :completed).to_sym 89 | 90 | # Retrieve the specified payment method or default to :credit_card 91 | payment_method = env.fetch("PAYMENT_METHOD", :credit_card).to_sym 92 | 93 | # Set a random transaction id 94 | order["TransacaoID"] = Digest::MD5.hexdigest(Time.now.to_s) 95 | 96 | # Set note 97 | order["Anotacao"] = env["NOTE"].to_s 98 | 99 | # Retrieve index 100 | index = proc do |hash, value| 101 | if hash.respond_to?(:key) 102 | hash.key(value) 103 | else 104 | hash.index(value) 105 | end 106 | end 107 | 108 | # Set payment method and status 109 | order["TipoPagamento"] = index[PagSeguro::Notification::PAYMENT_METHOD, payment_method] 110 | order["StatusTransacao"] = index[PagSeguro::Notification::STATUS, status] 111 | 112 | # Finally, ping the configured return URL 113 | uri = URI.parse File.join(PagSeguro.config["base"], PagSeguro.config["return_to"]) 114 | Net::HTTP.post_form uri, order 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /spec/pagseguro/rake_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "spec_helper" 3 | 4 | class Net::HTTP 5 | def self.post_form(uri, params) 6 | $HTTP_URI = uri 7 | $HTTP_PARAMS = params 8 | end 9 | end 10 | 11 | describe PagSeguro::Rake do 12 | before do 13 | # First, copy file from spec/support/pagseguro-test.yml 14 | # to tmp/pagseguro-test.yml 15 | @origin = File.dirname(__FILE__) + "/../support/pagseguro-test.yml" 16 | @destiny = Rails.root.join("tmp/pagseguro-test.yml") 17 | 18 | FileUtils.cp @origin, @destiny 19 | 20 | # Stub Digest::MD5#hexdigest to always return THEHASH 21 | Digest::MD5.stub :hexdigest => "THEHASH" 22 | 23 | # Stub the URI#parse to return a mock 24 | @uri = mock("URI").as_null_object 25 | URI.stub :parse => @uri 26 | 27 | # Load the pagseguro-test.yml file to 28 | # set some variables in order to compare it 29 | @orders = YAML.load_file(@origin) 30 | @order = @orders["ABC"] 31 | 32 | # Set the order id 33 | ENV["ID"] = "ABC" 34 | 35 | PagSeguro::Rake.run 36 | end 37 | 38 | it "should ping return URL with default arguments" do 39 | params["StatusTransacao"].should == "Completo" 40 | params["TipoPagamento"].should == "Cartão de Crédito" 41 | end 42 | 43 | it "should ping return URL with provided arguments" do 44 | ENV["STATUS"] = "canceled" 45 | ENV["PAYMENT_METHOD"] = "invoice" 46 | 47 | PagSeguro::Rake.run 48 | 49 | params["StatusTransacao"].should == "Cancelado" 50 | params["TipoPagamento"].should == "Boleto" 51 | end 52 | 53 | it "should set order id" do 54 | params["Referencia"].should == "ABC" 55 | end 56 | 57 | it "should set number of items" do 58 | params["NumItens"].should == 4 59 | end 60 | 61 | it "should set note" do 62 | ENV["NOTE"] = "Deliver ASAP" 63 | PagSeguro::Rake.run 64 | params["Anotacao"].should == "Deliver ASAP" 65 | end 66 | 67 | it "should set client's name" do 68 | ENV["NAME"] = "Rafael Mendonça França" 69 | PagSeguro::Rake.run 70 | params["CliNome"].should == "Rafael Mendonça França" 71 | end 72 | 73 | it "should set transaction date" do 74 | now = Time.now 75 | Time.stub :now => now 76 | 77 | PagSeguro::Rake.run 78 | params["DataTransacao"].should == now.strftime("%d/%m/%Y %H:%M:%S") 79 | end 80 | 81 | it "should set transaction id" do 82 | params["TransacaoID"].should == "THEHASH" 83 | end 84 | 85 | it "should set products" do 86 | params["ProdID_1"].should == "1" 87 | params["ProdDescricao_1"].should == "Ruby 1.9 PDF" 88 | params["ProdValor_1"].should == "9,00" 89 | params["ProdQuantidade_1"].should == "1" 90 | params["ProdExtras_1"].should == "0,00" 91 | params["ProdFrete_1"].should == "0,00" 92 | 93 | params["ProdID_2"].should == "2" 94 | params["ProdDescricao_2"].should == "Ruby 1.9 Screencast" 95 | params["ProdValor_2"].should == "12,50" 96 | params["ProdQuantidade_2"].should == "1" 97 | params["ProdExtras_2"].should == "0,00" 98 | params["ProdFrete_2"].should == "0,00" 99 | 100 | params["ProdID_3"].should == "3" 101 | params["ProdDescricao_3"].should == "Ruby T-Shirt" 102 | params["ProdValor_3"].should == "19,89" 103 | params["ProdQuantidade_3"].should == "2" 104 | params["ProdExtras_3"].should == "0,00" 105 | params["ProdFrete_3"].should == "2,50" 106 | 107 | params["ProdID_4"].should == "4" 108 | params["ProdDescricao_4"].should == "Ruby Mug" 109 | params["ProdValor_4"].should == "15,99" 110 | params["ProdQuantidade_4"].should == "1" 111 | params["ProdExtras_4"].should == "0,00" 112 | params["ProdFrete_4"].should == "0,00" 113 | end 114 | 115 | it "should set client info" do 116 | params["CliNome"].should_not be_blank 117 | params["CliEmail"].should_not be_blank 118 | params["CliEndereco"].should_not be_blank 119 | params["CliNumero"].should_not be_blank 120 | params["CliComplemento"].should_not be_blank 121 | params["CliBairro"].should_not be_blank 122 | params["CliCidade"].should_not be_blank 123 | params["CliCEP"].should match(/\d{5}-\d{3}/) 124 | params["CliTelefone"].should match(/\(\d{2}\) \d{4}-\d{4}/) 125 | end 126 | 127 | it "should set client e-mail" do 128 | ENV["EMAIL"] = "john@doe.com" 129 | PagSeguro::Rake.run 130 | params["CliEmail"].should == "john@doe.com" 131 | end 132 | 133 | it "should be test environment" do 134 | PagSeguro::DeveloperController::PAGSEGURO_ORDERS_FILE.should == File.join(Rails.root, "tmp", "pagseguro-test.yml") 135 | end 136 | 137 | private 138 | def params 139 | $HTTP_PARAMS 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /spec/helpers/helper_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require "spec_helper" 3 | 4 | describe PagSeguro::Helper do 5 | before do 6 | @order = PagSeguro::Order.new("I1001") 7 | PagSeguro.stub :developer? 8 | end 9 | 10 | subject { 11 | Nokogiri::HTML(helper.pagseguro_form(@order)).css("form").first 12 | } 13 | 14 | context "with default attributes" do 15 | it { should have_attr("action", PagSeguro::GATEWAY_URL) } 16 | it { should have_attr("class", "pagseguro") } 17 | it { should have_input(:name => "encoding", :value => "UTF-8") } 18 | it { should have_input(:name => "tipo", :value => "CP") } 19 | it { should have_input(:name => "moeda", :value => "BRL") } 20 | it { should have_input(:name => "ref_transacao", :value => "I1001") } 21 | it { should_not have_input(:name => "tipo_frete") } 22 | it { should have_input(:name => "email_cobranca", :value => "john@doe.com") } 23 | it { should have_input(:type => "submit", :value => "Pagar com PagSeguro") } 24 | end 25 | 26 | it "should include shipping type" do 27 | @order.shipping_type = "SD" 28 | subject.should have_input(:name => "tipo_frete", :value => "SD") 29 | end 30 | 31 | context "with custom attributes" do 32 | subject { 33 | Nokogiri::HTML(helper.pagseguro_form(@order, :submit => "Pague agora!", :email => "mary@example.com")).css("form").first 34 | } 35 | 36 | it { should have_input(:name => "email_cobranca", :value => "mary@example.com") } 37 | it { should have_input(:type => "submit", :value => "Pague agora!") } 38 | end 39 | 40 | context "with minimum product info" do 41 | before do 42 | @order << { :id => 1001, :price => 10.00, :description => "Rails 3 e-Book" } 43 | end 44 | 45 | it { should have_input(:name => "item_quant_1", :value => "1") } 46 | it { should have_input(:name => "item_id_1", :value => "1001") } 47 | it { should have_input(:name => "item_valor_1", :value => "1000") } 48 | it { should have_input(:name => "item_descr_1", :value => "Rails 3 e-Book") } 49 | it { should_not have_input(:name => "item_peso_1") } 50 | it { should_not have_input(:name => "item_frete_1") } 51 | end 52 | 53 | context "with optional product info" do 54 | before do 55 | @order << { :id => 1001, :price => 10.00, :description => "T-Shirt", :weight => 300, :shipping => 8.50, :quantity => 2 } 56 | end 57 | 58 | it { should have_input(:name => "item_quant_1", :value => "2") } 59 | it { should have_input(:name => "item_peso_1", :value => "300") } 60 | it { should have_input(:name => "item_frete_1", :value => "850") } 61 | end 62 | 63 | context "with multiple products" do 64 | before do 65 | @order << { :id => 1001, :price => 10.00, :description => "Rails 3 e-Book" } 66 | @order << { :id => 1002, :price => 19.00, :description => "Rails 3 e-Book + Screencast" } 67 | end 68 | 69 | it { should have_input(:name => "item_quant_1", :value => "1") } 70 | it { should have_input(:name => "item_id_1", :value => "1001") } 71 | it { should have_input(:name => "item_valor_1", :value => "1000") } 72 | it { should have_input(:name => "item_descr_1", :value => "Rails 3 e-Book") } 73 | 74 | it { should have_input(:name => "item_quant_2", :value => "1") } 75 | it { should have_input(:name => "item_id_2", :value => "1002") } 76 | it { should have_input(:name => "item_valor_2", :value => "1900") } 77 | it { should have_input(:name => "item_descr_2", :value => "Rails 3 e-Book + Screencast") } 78 | end 79 | 80 | context "with billing info" do 81 | before do 82 | @order.billing = { 83 | :name => "John Doe", 84 | :email => "john@doe.com", 85 | :address_zipcode => "01234-567", 86 | :address_street => "Rua Orobó", 87 | :address_number => 72, 88 | :address_complement => "Casa do fundo", 89 | :address_neighbourhood => "Tenório", 90 | :address_city => "Pantano Grande", 91 | :address_state => "AC", 92 | :address_country => "Brasil", 93 | :phone_area_code => "22", 94 | :phone_number => "1234-5678" 95 | } 96 | end 97 | 98 | it { should have_input(:name => "cliente_nome", :value => "John Doe") } 99 | it { should have_input(:name => "cliente_email", :value => "john@doe.com") } 100 | it { should have_input(:name => "cliente_cep", :value => "01234-567") } 101 | it { should have_input(:name => "cliente_end", :value => "Rua Orobó") } 102 | it { should have_input(:name => "cliente_num", :value => "72") } 103 | it { should have_input(:name => "cliente_compl", :value => "Casa do fundo") } 104 | it { should have_input(:name => "cliente_bairro", :value => "Tenório") } 105 | it { should have_input(:name => "cliente_cidade", :value => "Pantano Grande") } 106 | it { should have_input(:name => "cliente_uf", :value => "AC") } 107 | it { should have_input(:name => "cliente_pais", :value => "Brasil") } 108 | it { should have_input(:name => "cliente_ddd", :value => "22") } 109 | it { should have_input(:name => "cliente_tel", :value => "1234-5678") } 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/pagseguro/notification.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module PagSeguro 3 | class Notification 4 | API_URL = "https://pagseguro.uol.com.br/Security/NPI/Default.aspx" 5 | 6 | # Map all the attributes from PagSeguro. 7 | # 8 | MAPPING = { 9 | :payment_method => "TipoPagamento", 10 | :order_id => "Referencia", 11 | :processed_at => "DataTransacao", 12 | :status => "StatusTransacao", 13 | :transaction_id => "TransacaoID", 14 | :shipping_type => "TipoFrete", 15 | :shipping => "ValorFrete", 16 | :notes => "Anotacao" 17 | } 18 | 19 | # Map order status from PagSeguro. 20 | # 21 | STATUS = { 22 | "Completo" => :completed, 23 | "Aguardando Pagto" => :pending, 24 | "Aprovado" => :approved, 25 | "Em Análise" => :verifying, 26 | "Cancelado" => :canceled, 27 | "Devolvido" => :refunded 28 | } 29 | 30 | # Map payment method from PagSeguro. 31 | # 32 | PAYMENT_METHOD = { 33 | "Cartão de Crédito" => :credit_card, 34 | "Boleto" => :invoice, 35 | "Pagamento" => :pagseguro, 36 | "Pagamento Online" => :online_transfer, 37 | "Doação" => :donation 38 | } 39 | 40 | # The Rails params hash. 41 | # 42 | attr_accessor :params 43 | 44 | # Expects the params object from the current request. 45 | # PagSeguro will send POST with ISO-8859-1 encoded data, 46 | # so we need to normalize it to UTF-8. 47 | # 48 | def initialize(params, token = nil) 49 | @token = token 50 | @params = PagSeguro.developer? ? params : normalize(params) 51 | end 52 | 53 | # Normalize the specified hash converting all data to UTF-8. 54 | # 55 | def normalize(hash) 56 | each_value(hash) do |value| 57 | Utils.to_utf8(value) 58 | end 59 | end 60 | 61 | # Denormalize the specified hash converting all data to ISO-8859-1. 62 | # 63 | def denormalize(hash) 64 | each_value(hash) do |value| 65 | Utils.to_iso8859(value) 66 | end 67 | end 68 | 69 | # Return a list of products sent by PagSeguro. 70 | # The values will be normalized 71 | # (e.g. currencies will be converted to cents, quantity will be an integer) 72 | # 73 | def products 74 | @products ||= begin 75 | items = [] 76 | 77 | for i in (1..params["NumItens"].to_i) 78 | items << { 79 | :id => params["ProdID_#{i}"], 80 | :description => params["ProdDescricao_#{i}"], 81 | :quantity => params["ProdQuantidade_#{i}"].to_i, 82 | :price => to_price(params["ProdValor_#{i}"]), 83 | :shipping => to_price(params["ProdFrete_#{i}"]), 84 | :fees => to_price(params["ProdExtras_#{i}"]) 85 | } 86 | end 87 | 88 | items 89 | end 90 | end 91 | 92 | # Return the shipping fee. 93 | # Will be converted to a float number. 94 | # 95 | def shipping 96 | to_price mapping_for(:shipping) 97 | end 98 | 99 | # Return the order status. 100 | # Will be mapped to the STATUS constant. 101 | # 102 | def status 103 | @status ||= STATUS[mapping_for(:status)] 104 | end 105 | 106 | # Return the payment method. 107 | # Will be mapped to the PAYMENT_METHOD constant. 108 | # 109 | def payment_method 110 | @payment_method ||= PAYMENT_METHOD[mapping_for(:payment_method)] 111 | end 112 | 113 | # Parse the processing date to a Ruby object. 114 | # 115 | def processed_at 116 | @processed_at ||= begin 117 | groups = *mapping_for(:processed_at).match(/(\d{2})\/(\d{2})\/(\d{4}) ([\d:]+)/sm) 118 | Time.parse("#{groups[3]}-#{groups[2]}-#{groups[1]} #{groups[4]}") 119 | end 120 | end 121 | 122 | # Return the buyer info. 123 | # 124 | def buyer 125 | @buyer ||= { 126 | :name => params["CliNome"], 127 | :email => params["CliEmail"], 128 | :phone => { 129 | :area_code => params["CliTelefone"].to_s.split(" ").first, 130 | :number => params["CliTelefone"].to_s.split(" ").last 131 | }, 132 | :address => { 133 | :street => params["CliEndereco"], 134 | :number => params["CliNumero"], 135 | :complements => params["CliComplemento"], 136 | :neighbourhood => params["CliBairro"], 137 | :city => params["CliCidade"], 138 | :state => params["CliEstado"], 139 | :postal_code => params["CliCEP"] 140 | } 141 | } 142 | end 143 | 144 | def method_missing(method, *args) 145 | return mapping_for(method) if MAPPING[method] 146 | super 147 | end 148 | 149 | def respond_to?(method, include_private = false) 150 | return true if MAPPING[method] 151 | super 152 | end 153 | 154 | # A wrapper to the params hash, 155 | # sanitizing the return to symbols. 156 | # 157 | def mapping_for(name) 158 | params[MAPPING[name]] 159 | end 160 | 161 | # Cache the validation. 162 | # To bypass the cache, just provide an argument that is evaluated as true. 163 | # 164 | # invoice.valid? 165 | # invoice.valid?(:nocache) 166 | # 167 | def valid?(force=false) 168 | @valid = nil if force 169 | @valid = validates? if @valid.nil? 170 | @valid 171 | end 172 | 173 | # Return all useful properties in a single hash. 174 | # 175 | def to_hash 176 | MAPPING.inject({}) do |buffer, (name,value)| 177 | buffer.merge(name => __send__(name)) 178 | end 179 | end 180 | 181 | private 182 | def each_value(hash, &blk) # :nodoc: 183 | hash.each do |key, value| 184 | if value.kind_of?(Hash) 185 | hash[key] = each_value(value, &blk) 186 | else 187 | hash[key] = blk.call value 188 | end 189 | end 190 | 191 | hash 192 | end 193 | 194 | # Convert amount format to float. 195 | # 196 | def to_price(amount) 197 | amount = "0#{amount}" if amount =~ /^\,/ 198 | amount.to_s.gsub(/[^\d]/, "").gsub(/^(\d+)(\d{2})$/, '\1.\2').to_f 199 | end 200 | 201 | # Check if the provided data is valid by requesting the 202 | # confirmation API url. The request will always be valid while running in 203 | # developer mode. 204 | # 205 | def validates? 206 | return true if PagSeguro.developer? 207 | 208 | # include the params to validate our request 209 | request_params = params.merge({ 210 | :Comando => "validar", 211 | :Token => @token || PagSeguro.config["authenticity_token"] 212 | }).dup 213 | 214 | # do the request 215 | uri = URI.parse(API_URL) 216 | http = Net::HTTP.new(uri.host, uri.port) 217 | http.use_ssl = true 218 | http.verify_mode = OpenSSL::SSL::VERIFY_PEER 219 | http.ca_file = File.dirname(__FILE__) + "/cacert.pem" 220 | 221 | request = Net::HTTP::Post.new(uri.path) 222 | request.form_data = denormalize(request_params) 223 | response = http.start {|r| r.request request } 224 | (response.body =~ /VERIFICADO/) != nil 225 | end 226 | end 227 | end 228 | -------------------------------------------------------------------------------- /lib/pagseguro/faker.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | module PagSeguro 3 | module Faker 4 | extend self 5 | 6 | NAMES = [ 7 | "Agatha", "Alana", "Alexandre", "Alice", "Aline", "Alícia", "Amanda", "Ana", "Ana", "André", 8 | "Anthony", "Antônio", "Arthur", "Augusto", "Beatriz", "Benjamin", "Benício", "Bernardo", 9 | "Bianca", "Brenda", "Breno", "Bruna", "Bruno", "Bryan", "Bárbara", "Caio", "Calebe", "Camila", 10 | "Carlos", "Carolina", "Catarina", "Cecília", "Clara", "Clarice", "César", "Daniel", "Danilo", 11 | "Davi", "Diego", "Diogo", "Eduarda", "Eduardo", "Elisa", "Eloá", "Emanuel", "Emanuela", 12 | "Emily", "Enrico", "Enzo", "Erick", "Esther", "Evelyn", "Felipe", "Felipe", "Fernanda", 13 | "Fernando", "Francisco", "Gabriel", "Gabriela", "Giovanna", "Giovanni", "Guilherme", 14 | "Gustavo", "Heitor", "Helena", "Heloísa", "Henrique", "Henry", "Hugo", "Igor", "Isaac", 15 | "Isabel", "Isabella", "Isadora", "Joana", "Joaquim", "Jonathan", "João", "Juan", "Juliana", 16 | "Júlia", "Júlio", "Kamilly", "Kauã", "Kauê", "Kaíque", "Kevin", "Kyara", "Lara", "Larissa", 17 | "Laura", "Lavínia", "Laís", "Leonardo", "Letícia", "Levi", "Lorena", "Lorenzo", "Luan", 18 | "Luana", "Luca", "Lucas", "Luigi", "Luiz", "Luiza", "Luna", "Lívia", "Maitê", "Manuela", 19 | "Marcela", "Marco", "Marcos", "Maria", "Mariana", "Marina", "Matheus", "Melissa", "Miguel", 20 | "Milena", "Mirella", "Murilo", "Nathalia", "Nathan", "Nicolas", "Nicole", "Nina", "Olívia", 21 | "Otávio", "Paulo", "Pedro", "Pietra", "Rafael", "Rafaela", "Raquel", "Raul", "Rayssa", 22 | "Rebeca", "Renan", "Ricardo", "Rodrigo", "Ryan", "Sabrina", "Samuel", "Sarah", "Sophia", 23 | "Sophie", "Stefany", "Stella", "Thais", "Thayla", "Theo", "Thomas", "Tiago", "Valentina", 24 | "Vicente", "Vinicius", "Vitor", "Vitória", "Yago", "Yan", "Yasmin", "Yuri", "Ísis" 25 | ] 26 | 27 | SURNAMES = [ 28 | "Abreu", "Aguiar", "Alencar", "Almeida", "Alves", "Amaral", "Ambrósio", "Amorim", "Amorim Neto", 29 | "Andrade", "Andrade Filho", "Antunes", "Aparício", "Araújo", "Assis", "Azevedo", "Baltazar", 30 | "Barbosa", "Bardini", "Barni", "Barra", "Barreto", "Barros", "Bastos", "Batista", "Belarmino", 31 | "Bento Da Silva", "Bezerra", "Bianchini", "Bittencourt", "Boaventura", "Bonfim", "Borges", 32 | "Braga", "Branco", "Brasil", "Brito", "Brunelli", "Cachoeira", "Caetano", "Camargo", 33 | "Campos", "Canella", "Cardoso", "Carvalho", "Castilho", "Castro", "Coelho", "Correia", "Costa", 34 | "Cândido", "de Sá", "Delfino", "do Vale", "Duarte", "Dutra", "Espindola", "Facchini", "Fagundes", 35 | "Farias", "Fernandes", "Ferreira", "Fonseca", "Fortunato", "Franz", "França", "Freitas", 36 | "Gomes", "Gonçalves", "Goulart", "Guedes", "Guerra", "Guimarães", "Hungaro", "Justino", "Leal", 37 | "Leite", "Lima", "Linhares", "Liz", "Lombardi", "Lopes", "Macedo", "Machado", "Maia", "Manhães", 38 | "Marques", "Martins", "Mendes", "Molinari", "Monteiro", "Morais", "Moreira", "Motta", "Neves", 39 | "Nunes", "Oliveira", "Pacheco", "Paiva", "Pinheiro", "Pinto", "Ribeiro", "Rocha", "Rodrigues", 40 | "Salles", "Santos", "Silva", "Souza", "Teixeira", "Vaz", "Ventura", "Vieira" 41 | ] 42 | 43 | CITIES = [ 44 | "Augustinópolis", "Alegre", "Anitápolis", "Aroeiras do Itaim", "Auriflama", "Areia Branca", 45 | "Areial", "Avelinópolis", "Açucena", "Álvaro de Carvalho", "Araguaína", "Arroio do Tigre", 46 | "Anguera", "Águas Mornas", "Aquiraz", "Belmonte", "Barracão", "Barreira", "Belém de Maria", 47 | "Barra do Chapéu", "Bandeira do Sul", "Biquinhas", "Baependi", "Bela Vista de Goiás", 48 | "Buriti dos Montes", "Bom Lugar", "Brasil Novo", "Bocaiuva", "Bela Vista do Maranhão", 49 | "Bom Sucesso de Itararé", "Caridade", "Capela", "Colombo", "Celso Ramos", "Carmolândia", 50 | "Conceição do Castelo", "Camargo", "Carnaíba", "Carambeí", "Cametá", "Conceição de Macabu", 51 | "Coronel Martins", "Capela", "Campina Verde", "Curiúva", "Divisópolis", "Darcinópolis", 52 | "Divinolândia de Minas", "Dores do Turvo", "Dom Cavati", "Delfim Moreira", "Dobrada", 53 | "Dona Emma", "Dionísio", "Delmiro Gouveia", "Dona Inês", "Dom Feliciano", "Datas", 54 | "Divisa Alegre", "Dom Eliseu", "Ermo", "Escada", "Embaúba", "Encantado", "Elias Fausto", 55 | "Embu", "Espírito Santo", "Encruzilhada", "Engenheiro Paulo de Frontin", "Equador", 56 | "Erebango", "Estiva", "Esmeraldas", "Espera Feliz", "Eugênio de Castro", "Formoso", "Faro", 57 | "Frutuoso Gomes", "Formoso do Araguaia", "Floreal", "Francisco Alves", "Fartura do Piauí", 58 | "Ferreira Gomes", "Florestal", "Fontoura Xavier", "Fernandes Pinheiro", "Francisco Macedo", 59 | "Fortaleza dos Valos", "Formigueiro", "Feira Grande", "Granito", "Gália", "General Carneiro", 60 | "Graça", "Guarda-Mor", "Glória de Dourados", "Guarantã", "Gurjão", "Guareí", 61 | "Governador Eugênio Barros", "Guarani das Missões", "Guaíba", "Glorinha", "Gilbués", 62 | "Granja", "Horizontina", "Honório Serpa", "Hortolândia", "Heliópolis", "Horizonte", 63 | "Hugo Napoleão", "Holambra", "Heitoraí", "Humaitá", "Humberto de Campos", "Hulha Negra", 64 | "Humaitá", "Heliodora", "Herval", "Hidrolândia", "Itajá", "Inimutaba", "Itauçu", 65 | "Itaporã do Tocantins", "Ivoti", "Ipaussu", "Itapirapuã", "Itiúba", "Itatinga", "Ipanema", 66 | "Itaporanga", "Itamarati", "Itabira", "Imbé", "Iacri", "Japi", "Júlio Borges", "Jaciara", 67 | "Jesuânia", "Jenipapo dos Vieiras", "Jucurutu", "Jaguaquara", "Jacobina do Piauí", 68 | "Jaguariaíva", "Jaupaci", "Jatobá", "Juranda", "José Bonifácio", "Joaquim Távora", 69 | "Jandira", "Kaloré", "Lagoinha", "Lambari d'Oeste", "Luís Alves", "Limoeiro", 70 | "Lagoa do Piauí", "Liberato Salzano", "Luzerna", "Luciara", "Lindoeste", "Luislândia", 71 | "Lajedinho", "Luzinópolis", "Lapa", "Lagoa Santa", "Luís Gomes", "Maratá", "Muniz Ferreira", 72 | "Mato Verde", "Minaçu", "Marabá Paulista", "Monte Alegre", "Maripá", "Matias Cardoso", 73 | "Mirandópolis", "Moema", "Monsenhor Tabosa", "Minador do Negrão", "Monte Santo de Minas", 74 | "Miracatu", "Morada Nova", "Ninheira", "Nova União", "Niterói", "Nova Aurora", "Nova América", 75 | "Nova Era", "Nova Aliança do Ivaí", "Nova Santa Helena", "Natércia", "Nova Tebas", "Natuba", 76 | "Novo Hamburgo", "Nova Laranjeiras", "Neves Paulista", "Nova Candelária", "Ouro Velho", 77 | "Ouro Verde do Oeste", "Ouro Fino", "Ourilândia do Norte", "Oeiras do Pará", 78 | "Onça de Pitangui", "Oiapoque", "Óleo", "Olho d'Água do Piauí", "Olho d'Água do Casado", 79 | "Ocauçu", "Ouro Verde", "Oliveira Fortes", "Orobó", "Osvaldo Cruz", "Pacatuba", "Peabiru", 80 | "Portel", "Paverama", "Poção de Pedras", "Pirapemas", "Palestina", "Pedrinópolis", 81 | "Pirpirituba", "Pantano Grande", "Pitangueiras", "Paulistânia", "Paulistas", 82 | "Pedra Bonita", "Planalto da Serra", "Quixeré", "Quarto Centenário", "Quilombo", 83 | "Quixaba", "Quatis", "Quixadá", "Quissamã", "Queluzito", "Quixabeira", "Quijingue", 84 | "Quitandinha", "Queimada Nova", "Quixeramobim", "Queimadas", "Quinta do Sol", "Rio Preto", 85 | "Ronda Alta", "Rio Bananal", "Rio Rufino", "Rondolândia", "Riacho de Santo Antônio", 86 | "Rolim de Moura", "Riqueza", "Raposa", "Rio das Antas", "Riacho dos Machados", "Ribeirópolis", 87 | "Raul Soares", "Rio Grande da Serra", "Roseira", "São João", "Santa Rita do Sapucaí", 88 | "Salto do Jacuí", "Saúde", "Senador Georgino Avelino", "São Sebastião do Oeste", "São Pedro", 89 | "Santa Cecília", "São José dos Quatro Marcos", "Sapé", "São Felipe d'Oeste", 90 | "São Gonçalo do Amarante", "Siriri", "Sales Oliveira", "Santa Terezinha", "Timbó Grande", 91 | "Tamboara", "Taguaí", "Trairão", "Theobroma", "Taperoá", "Torre de Pedra", "Teutônia", 92 | "Taquarivaí", "Tanhaçu", "Tenório", "Três Lagoas", "Taperoá", "Terra Santa", 93 | "Tangará da Serra", "Urupá", "Uiraúna", "Uchoa", "Uruoca", "União", "Uruguaiana", 94 | "Uarini", "Ubaíra", "Una", "Urucará", "Uberaba", "Uru", "Umbuzeiro", "União Paulista", 95 | "Umuarama", "Varjota", "Virgolândia", "Viçosa do Ceará", "Vera Cruz", "Vespasiano", 96 | "Vista Gaúcha", "Vila Propício", "Vargem", "Vista Serrana", "Várzea Nova", 97 | "Visconde do Rio Branco", "Várzea da Roça", "Vicência", "Venâncio Aires", "Vera", 98 | "Witmarsum", "Wenceslau Guimarães", "Westfália", "Wenceslau Braz", "Wagner", "Wanderley", 99 | "Wenceslau Braz", "Wall Ferraz", "Wanderlândia", "Xapuri", "Xangri-lá", "Xinguara", "Xaxim", 100 | "Xambioá", "Xambrê", "Xexéu", "Xavantina", "Xanxerê", "Xique-Xique", "Zortéa", "Zabelê", 101 | "Zé Doca", "Zacarias" 102 | ] 103 | 104 | STATES = [ 105 | "Acre", "Alagoas", "Amapá", "Amazonas", "Bahia", "Ceará", "Distrito Federal", "Espírito Santo", 106 | "Goiás", "Maranhão", "Mato Grosso", "Mato Grosso do Sul", "Minas Gerais", "Pará", "Paraíba", 107 | "Paraná", "Pernambuco", "Piauí", "Rio de Janeiro", "Rio Grande do Norte", "Rio Grande do Sul", 108 | "Rondônia", "Roraima", "Santa Catarina", "São Paulo", "Sergipe", "Tocantins" 109 | ] 110 | 111 | EMAILS = [ 112 | "gmail.com", "yahoo.com.br", "hotmail.com", "uol.com.br", "ig.com.br", "bol.com.br" 113 | ] 114 | 115 | STREET_TYPES = ["Rua", "Avenida", "Estrada", "Alameda"] 116 | 117 | SECONDARY_ADDRESS = ["Apto", "Casa", "Bloco"] 118 | 119 | def street_name 120 | "#{STREET_TYPES.sample} #{CITIES.sample}" 121 | end 122 | 123 | def secondary_address 124 | "#{SECONDARY_ADDRESS.sample} #{rand(1000)}" 125 | end 126 | 127 | def phone_number(format = "(##) ####-####") 128 | format.gsub(/#/) { (1..9).to_a.sample } 129 | end 130 | 131 | def zipcode 132 | "#####-###".gsub(/#/) { (0..9).to_a.sample } 133 | end 134 | 135 | def email(base = nil) 136 | base ||= full_name 137 | base = normalize(base.downcase).gsub(/-/, ".") 138 | "#{base}@#{EMAILS.sample}" 139 | end 140 | 141 | def name 142 | NAMES.sample 143 | end 144 | 145 | def surname 146 | SURNAMES.sample 147 | end 148 | 149 | def full_name 150 | "#{name} #{surname}" 151 | end 152 | 153 | def city 154 | CITIES.sample 155 | end 156 | 157 | def state 158 | STATES.sample 159 | end 160 | 161 | private 162 | def normalize(str) 163 | str = ActiveSupport::Multibyte::Chars.new(str.dup) 164 | str = str.normalize(:kd).gsub(/[^\x00-\x7F]/, "").to_s 165 | str.gsub!(/[^-\w\d]+/xim, "-") 166 | str.gsub!(/-+/xm, "-") 167 | str.gsub!(/^-?(.*?)-?$/, '\1') 168 | str.downcase! 169 | str 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /spec/pagseguro/notification_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "spec_helper" 3 | 4 | describe PagSeguro::Notification do 5 | subject { PagSeguro::Notification.new(@the_params) } 6 | let(:payload) { YAML.load_file File.dirname(__FILE__) + "/../fixtures/notification.yml" } 7 | before { @the_params = {} } 8 | 9 | it "should not request the confirmation url when running developer mode" do 10 | PagSeguro.stub :developer? => true 11 | Net::HTTP.should_not_receive(:new) 12 | subject.should be_valid 13 | end 14 | 15 | describe "#to_hash" do 16 | subject { PagSeguro::Notification.new(payload) } 17 | 18 | PagSeguro::Notification::MAPPING.each do |name, value| 19 | it "includes #{name}" do 20 | subject.to_hash.should have_key(name) 21 | subject.to_hash[name].should_not be_nil 22 | end 23 | end 24 | end 25 | 26 | describe "status mapping" do 27 | it "should be completed" do 28 | set_status!("Completo") 29 | subject.status.should == :completed 30 | end 31 | 32 | it "should be pending" do 33 | set_status!("Aguardando Pagto") 34 | subject.status.should == :pending 35 | end 36 | 37 | it "should be approved" do 38 | set_status!("Aprovado") 39 | subject.status.should == :approved 40 | end 41 | 42 | it "should be verifying" do 43 | set_status!("Em Análise") 44 | subject.status.should == :verifying 45 | end 46 | 47 | it "should be canceled" do 48 | set_status!("Cancelado") 49 | subject.status.should == :canceled 50 | end 51 | 52 | it "should be refunded" do 53 | set_status!("Devolvido") 54 | subject.status.should == :refunded 55 | end 56 | end 57 | 58 | describe "payment mapping" do 59 | it "should be credit card" do 60 | set_payment!("Cartão de Crédito") 61 | subject.payment_method.should == :credit_card 62 | end 63 | 64 | it "should be invoice" do 65 | set_payment!("Boleto") 66 | subject.payment_method.should == :invoice 67 | end 68 | 69 | it "should be pagseguro" do 70 | set_payment!("Pagamento") 71 | subject.payment_method.should == :pagseguro 72 | end 73 | 74 | it "should be online transfer" do 75 | set_payment!("Pagamento Online") 76 | subject.payment_method.should == :online_transfer 77 | end 78 | 79 | it "should be donation" do 80 | set_payment!("Doação") 81 | subject.payment_method.should == :donation 82 | end 83 | end 84 | 85 | describe "buyer mapping" do 86 | it "should return client name" do 87 | param!("CliNome", "John Doe") 88 | subject.buyer[:name].should == "John Doe" 89 | end 90 | 91 | it "should return client email" do 92 | param!("CliEmail", "john@doe.com") 93 | subject.buyer[:email].should == "john@doe.com" 94 | end 95 | 96 | it "should return client phone" do 97 | param!("CliTelefone", "11 55551234") 98 | subject.buyer[:phone][:area_code].should == "11" 99 | subject.buyer[:phone][:number].should == "55551234" 100 | end 101 | 102 | describe "address" do 103 | it "should return street" do 104 | param!("CliEndereco", "Av. Paulista") 105 | subject.buyer[:address][:street].should == "Av. Paulista" 106 | end 107 | 108 | it "should return number" do 109 | param!("CliNumero", "2500") 110 | subject.buyer[:address][:number].should == "2500" 111 | end 112 | 113 | it "should return complements" do 114 | param!("CliComplemento", "Apto 123-A") 115 | subject.buyer[:address][:complements].should == "Apto 123-A" 116 | end 117 | 118 | it "should return neighbourhood" do 119 | param!("CliBairro", "Bela Vista") 120 | subject.buyer[:address][:neighbourhood].should == "Bela Vista" 121 | end 122 | 123 | it "should return city" do 124 | param!("CliCidade", "São Paulo") 125 | subject.buyer[:address][:city].should == "São Paulo" 126 | end 127 | 128 | it "should return state" do 129 | param!("CliEstado", "SP") 130 | subject.buyer[:address][:state].should == "SP" 131 | end 132 | 133 | it "should return postal code" do 134 | param!("CliCEP", "01310300") 135 | subject.buyer[:address][:postal_code].should == "01310300" 136 | end 137 | end 138 | end 139 | 140 | describe "other mappings" do 141 | it "should map the order id" do 142 | param!("Referencia", "ABCDEF") 143 | subject.order_id.should == "ABCDEF" 144 | end 145 | 146 | it "should map the processing date" do 147 | param!("DataTransacao", "04/09/2009 16:23:44") 148 | subject.processed_at.should == Time.parse("2009-09-04 16:23:44").utc 149 | end 150 | 151 | it "should map the shipping type" do 152 | param!("TipoFrete", "SD") 153 | subject.shipping_type.should == "SD" 154 | end 155 | 156 | it "should map the client annotation" do 157 | param!("Anotacao", "Gift package, please!") 158 | subject.notes.should == "Gift package, please!" 159 | end 160 | 161 | it "should map the shipping price" do 162 | param!("ValorFrete", "199,38") 163 | subject.shipping.should == 199.38 164 | 165 | param!("ValorFrete", "1.799,38") 166 | subject.shipping.should == 1799.38 167 | end 168 | 169 | it "should map the transaction id" do 170 | param!("TransacaoID", "ABCDEF") 171 | subject.transaction_id.should == "ABCDEF" 172 | end 173 | end 174 | 175 | describe "products" do 176 | before do 177 | @__products = [] 178 | end 179 | 180 | it "should map 5 products" do 181 | param!("NumItens", "5") 182 | subject.products.should have(5).items 183 | end 184 | 185 | it "should map 25 products" do 186 | param!("NumItens", "25") 187 | subject.products.should have(25).items 188 | end 189 | 190 | it "should set attributes with defaults" do 191 | set_product! :description => "Ruby 1.9 PDF", :price => "12,90", :id => 1 192 | p = subject.products.first 193 | 194 | p[:description].should == "Ruby 1.9 PDF" 195 | p[:price].should == 12.90 196 | p[:id].should == "1" 197 | p[:quantity].should == 1 198 | p[:fees].should be_zero 199 | p[:shipping].should be_zero 200 | end 201 | 202 | it "should set attributes with custom values" do 203 | set_product!({ 204 | :description => "Rails Application Templates", 205 | :price => "1,00", 206 | :id => 8, 207 | :fees => "2,53", 208 | :shipping => "3,50", 209 | :quantity => 10 210 | }) 211 | 212 | p = subject.products.first 213 | 214 | p[:description].should == "Rails Application Templates" 215 | p[:price].should == 1.00 216 | p[:id].should == "8" 217 | p[:quantity].should == 10 218 | p[:fees].should == 2.53 219 | p[:shipping].should == 3.50 220 | end 221 | 222 | specify "bug fix: should work correctly when price is 0.9" do 223 | set_product!({ 224 | :price => ",90", 225 | }) 226 | 227 | p = subject.products.first 228 | 229 | p[:price].should == 0.9 230 | end 231 | end 232 | 233 | describe "confirmation" do 234 | before do 235 | PagSeguro.stub :developer? => false 236 | @url = PagSeguro::Notification::API_URL 237 | subject.stub :api_url => @url 238 | end 239 | 240 | it "should be valid" do 241 | FakeWeb.register_uri(:post, @url, :body => "VERIFICADO") 242 | subject.should be_valid 243 | end 244 | 245 | it "should be invalid" do 246 | FakeWeb.register_uri(:post, @url, :body => "") 247 | subject.should_not be_valid 248 | end 249 | 250 | it "should force validation" do 251 | FakeWeb.register_uri(:post, @url, :body => "") 252 | subject.should_not be_valid 253 | 254 | FakeWeb.register_uri(:post, @url, :body => "VERIFICADO") 255 | subject.should_not be_valid 256 | subject.should be_valid(:nocache) 257 | end 258 | 259 | it "should set the authenticity token from the initialization" do 260 | notification = PagSeguro::Notification.new(@the_params, 'ABCDEF') 261 | 262 | post = mock("post").as_null_object 263 | post.should_receive(:form_data=).with({:Comando => "validar", :Token => "ABCDEF"}) 264 | 265 | Net::HTTP.should_receive(:new).and_return(mock("http").as_null_object) 266 | Net::HTTP::Post.should_receive(:new).and_return(post) 267 | 268 | notification.valid? 269 | end 270 | 271 | it "should set the authenticity token from the configuration" do 272 | PagSeguro.stub :config => {"authenticity_token" => "ABCDEF"} 273 | 274 | post = mock("post").as_null_object 275 | post.should_receive(:form_data=).with({:Comando => "validar", :Token => "ABCDEF"}) 276 | 277 | Net::HTTP.should_receive(:new).and_return(mock("http").as_null_object) 278 | Net::HTTP::Post.should_receive(:new).and_return(post) 279 | 280 | subject.valid? 281 | end 282 | 283 | it "should propagate params" do 284 | param!("VendedorEmail", "john@doe.com") 285 | param!("NumItens", "14") 286 | PagSeguro.stub :config => {"authenticity_token" => "ABCDEF"} 287 | 288 | post = mock("post").as_null_object 289 | post.should_receive(:form_data=).with({ 290 | :Comando => "validar", 291 | :Token => "ABCDEF", 292 | "VendedorEmail" => "john@doe.com", 293 | "NumItens" => "14" 294 | }) 295 | 296 | Net::HTTP.should_receive(:new).and_return(mock("http").as_null_object) 297 | Net::HTTP::Post.should_receive(:new).and_return(post) 298 | 299 | subject.valid? 300 | end 301 | end 302 | 303 | private 304 | def set_status!(value) 305 | param!("StatusTransacao", value) 306 | end 307 | 308 | def set_payment!(value) 309 | param!("TipoPagamento", value) 310 | end 311 | 312 | def param!(name, value) 313 | subject.params.merge!(name => value) 314 | end 315 | 316 | def set_product!(options={}) 317 | @__products ||= [] 318 | 319 | i = @__products.size + 1 320 | 321 | options = { 322 | :quantity => 1, 323 | :fees => "0,00", 324 | :shipping => "0,00" 325 | }.merge(options) 326 | 327 | @__products << { 328 | "ProdID_#{i}" => options[:id].to_s, 329 | "ProdDescricao_#{i}" => options[:description].to_s, 330 | "ProdValor_#{i}" => options[:price].to_s, 331 | "ProdFrete_#{i}" => options[:shipping].to_s, 332 | "ProdExtras_#{i}" => options[:fees].to_s, 333 | "ProdQuantidade_#{i}" => options[:quantity].to_s 334 | } 335 | 336 | subject.params.merge!(@__products.last) 337 | subject.params.merge!("NumItens" => i) 338 | @__products.last 339 | end 340 | end 341 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # PAGSEGURO 2 | 3 | [![Este projeto não está mais sendo mantido. Use pagseguro/ruby, a gem oficial.](http://messages.hellobits.com/error.svg?message=Este%20projeto%20n%C3%A3o%20est%C3%A1%20mais%20sendo%20mantido.%20Use%20pagseguro%2Fruby%2C%20a%20gem%20oficial.)](https://github.com/pagseguro/ruby) 4 | 5 | Este é um plugin do Ruby on Rails que permite utilizar o [PagSeguro](https://pagseguro.uol.com.br/?ind=689659), gateway de pagamentos do [UOL](http://uol.com.br). 6 | 7 | ## SOBRE O PAGSEGURO 8 | 9 | ### Carrinho Próprio 10 | 11 | Trabalhando com carrinho próprio, sua loja mantém os dados do carrinho. O processo de inclusão de produtos no carrinho de compras acontece no próprio site da loja. Quando o comprador quiser finalizar sua compra, ele é enviado ao PagSeguro uma única vez com todos os dados de seu pedido. Aqui também, você tem duas opções. Pode enviar os dados do pedido e deixar o PagSeguro solicitar os dados do comprador, ou pode solicitar todos os dados necessários para a compra em sua loja e enviá-los ao PagSeguro. 12 | 13 | ### Retorno Automático 14 | 15 | Após o processo de compra e pagamento, o usuário é enviado de volta a seu site. Para isso, você deve configurar uma [URL de retorno](https://pagseguro.uol.com.br/Security/ConfiguracoesWeb/RetornoAutomatico.aspx). 16 | 17 | Antes de enviar o usuário para essa URL, o robô do PagSeguro faz um POST para ela, em segundo plano, com os dados e status da transação. Lendo esse POST, você pode obter o status do pedido. Se o pagamento entrou em análise, ou se o usuário pagou usando boleto bancário, o status será "Aguardando Pagamento" ou "Em Análise". Nesses casos, quando a transação for confirmada (o que pode acontecer alguns dias depois) a loja receberá outro POST, informando o novo status. **Cada vez que a transação muda de status, um POST é enviado.** 18 | 19 | ## REQUISITOS 20 | 21 | A versão atual que está sendo mantida suporta Rails 3.0.0 ou superior. 22 | 23 | Se você quiser esta biblioteca em versão mais antigas do Rails (2.3, por exemplo) deverá usar o [branch legacy](http://github.com/fnando/pagseguro/tree/legacy), QUE NÃO É MAIS MANTIDO. 24 | 25 | ## COMO USAR 26 | 27 | ### Configuração 28 | 29 | O primeiro passo é instalar a biblioteca. Para isso, basta executar o comando 30 | 31 | gem install pagseguro 32 | 33 | Adicione a biblioteca ao arquivo Gemfile: 34 | 35 | ~~~.ruby 36 | gem "pagseguro", "~> 0.1.10" 37 | ~~~ 38 | 39 | Lembre-se de utilizar a versão que você acabou de instalar. 40 | 41 | Depois de instalar a biblioteca, você precisará executar gerar o arquivo de configuração, que deve residir em `config/pagseguro.yml`. Para gerar um arquivo de modelo execute 42 | 43 | rails generate pagseguro:install 44 | 45 | O arquivo de configuração gerado será parecido com isto: 46 | 47 | ~~~.yml 48 | development: &development 49 | developer: true 50 | base: "http://localhost:3000" 51 | return_to: "/pedido/efetuado" 52 | email: user@example.com 53 | 54 | test: 55 | <<: *development 56 | 57 | production: 58 | authenticity_token: 9CA8D46AF0C6177CB4C23D76CAF5E4B0 59 | email: user@example.com 60 | return_to: "/pedido/efetuado" 61 | ~~~ 62 | 63 | Esta gem possui um modo de desenvolvimento que permite simular a realização de pedidos e envio de notificações; basta utilizar a opção `developer`. Ela é ativada por padrão nos ambientes de desenvolvimento e teste. Você deve configurar as opções `base`, que deverá apontar para o seu servidor e a URL de retorno, que deverá ser configurada no próprio [PagSeguro](https://pagseguro.uol.com.br/?ind=689659), na página . 64 | 65 | Para o ambiente de produção, que irá efetivamente enviar os dados para o [PagSeguro](https://pagseguro.uol.com.br/?ind=689659), você precisará adicionar o e-mail cadastrado como vendedor e o `authenticity_token`, que é o Token para Conferência de Segurança, que pode ser conseguido na página . 66 | 67 | ### Montando o formulário 68 | 69 | Para montar o seu formulário, você deverá utilizar a classe `PagSeguro::Order`. Esta classe deverá ser instanciada recebendo um identificador único do pedido. Este identificador permitirá identificar o pedido quando o [PagSeguro](https://pagseguro.uol.com.br/?ind=689659) notificar seu site sobre uma alteração no status do pedido. 70 | 71 | ~~~.ruby 72 | class CartController < ApplicationController 73 | def checkout 74 | # Busca o pedido associado ao usuario; esta logica deve 75 | # ser implementada por voce, da maneira que achar melhor 76 | @invoice = current_user.invoices.last 77 | 78 | # Instanciando o objeto para geracao do formulario 79 | @order = PagSeguro::Order.new(@invoice.id) 80 | 81 | # adicionando os produtos do pedido ao objeto do formulario 82 | @invoice.products.each do |product| 83 | # Estes sao os atributos necessarios. Por padrao, peso (:weight) eh definido para 0, 84 | # quantidade eh definido como 1 e frete (:shipping) eh definido como 0. 85 | @order.add :id => product.id, :price => product.price, :description => product.title 86 | end 87 | end 88 | end 89 | ~~~ 90 | 91 | Se você precisar, pode definir o tipo de frete com o método `shipping_type`. 92 | 93 | ~~~.ruby 94 | @order.shipping_type = "SD" # Sedex 95 | @order.shipping_type = "EN" # PAC 96 | @order.shipping_type = "FR" # Frete Proprio 97 | ~~~ 98 | 99 | Se você precisar, pode definir os dados de cobrança com o método `billing`. 100 | 101 | ~~~.ruby 102 | @order.billing = { 103 | :name => "John Doe", 104 | :email => "john@doe.com", 105 | :address_zipcode => "01234-567", 106 | :address_street => "Rua Orobo", 107 | :address_number => 72, 108 | :address_complement => "Casa do fundo", 109 | :address_neighbourhood => "Tenorio", 110 | :address_city => "Pantano Grande", 111 | :address_state => "AC", 112 | :address_country => "Brasil", 113 | :phone_area_code => "22", 114 | :phone_number => "1234-5678" 115 | } 116 | ~~~ 117 | 118 | Depois que você definiu os produtos do pedido, você pode exibir o formulário. 119 | 120 | ~~~.erb 121 | 122 | <%= pagseguro_form @order, :submit => "Efetuar pagamento!" %> 123 | ~~~ 124 | 125 | Por padrão, o formulário é enviado para o email no arquivo de configuração. Você pode mudar o email com a opção `:email`. 126 | 127 | ~~~.erb 128 | <%= pagseguro_form @order, :submit => "Efetuar pagamento!", :email => @account.email %> 129 | ~~~ 130 | 131 | ### Recebendo notificações 132 | 133 | Toda vez que o status de pagamento for alterado, o [PagSeguro](https://pagseguro.uol.com.br/?ind=689659) irá notificar sua URL de retorno com diversos dados. Você pode interceptar estas notificações com o método `pagseguro_notification`. O bloco receberá um objeto da classe `PagSeguro::Notification` e só será executado se for uma notificação verificada junto ao [PagSeguro](https://pagseguro.uol.com.br/?ind=689659). 134 | 135 | ~~~.ruby 136 | class CartController < ApplicationController 137 | skip_before_filter :verify_authenticity_token 138 | 139 | def confirm 140 | return unless request.post? 141 | 142 | pagseguro_notification do |notification| 143 | # Aqui voce deve verificar se o pedido possui os mesmos produtos 144 | # que voce cadastrou. O produto soh deve ser liberado caso o status 145 | # do pedido seja "completed" ou "approved" 146 | end 147 | 148 | render :nothing => true 149 | end 150 | end 151 | ~~~ 152 | O método `pagseguro_notification` também pode receber como parâmetro o `authenticity_token` que será usado pra verificar a autenticação. 153 | 154 | ~~~.ruby 155 | class CartController < ApplicationController 156 | skip_before_filter :verify_authenticity_token 157 | 158 | def confirm 159 | return unless request.post? 160 | # Se voce receber pagamentos de contas diferentes, pode passar o 161 | # authenticity_token adequado como parametro para pagseguro_notification 162 | account = Account.find(params[:seller_id]) 163 | pagseguro_notification(account.authenticity_token) do |notification| 164 | end 165 | 166 | render :nothing => true 167 | end 168 | end 169 | ~~~ 170 | 171 | O objeto `notification` possui os seguintes métodos: 172 | 173 | * `PagSeguro::Notification#products`: Lista de produtos enviados na notificação. 174 | * `PagSeguro::Notification#shipping`: Valor do frete 175 | * `PagSeguro::Notification#status`: Status do pedido 176 | * `PagSeguro::Notification#payment_method`: Tipo de pagamento 177 | * `PagSeguro::Notification#processed_at`: Data e hora da transação 178 | * `PagSeguro::Notification#buyer`: Dados do comprador 179 | * `PagSeguro::Notification#valid?(force=false)`: Verifica se a notificação é válida, confirmando-a junto ao PagSeguro. A resposta é jogada em cache e pode ser forçada com `PagSeguro::Notification#valid?(:force)` 180 | 181 | **ATENÇÃO:** Não se esqueça de adicionar `skip_before_filter :verify_authenticity_token` ao controller que receberá a notificação; caso contrário, uma exceção será lançada. 182 | 183 | ### Utilizando modo de desenvolvimento 184 | 185 | Toda vez que você enviar o formulário no modo de desenvolvimento, um arquivo YAML será criado em `tmp/pagseguro-#{Rails.env}.yml`. Esse arquivo conterá todos os pedidos enviados. 186 | 187 | Depois, você será redirecionado para a URL de retorno que você configurou no arquivo `config/pagseguro.yml`. Para simular o envio de notificações, você deve utilizar a rake `pagseguro:notify`. 188 | 189 | $ rake pagseguro:notify ID= 190 | 191 | O ID do pedido deve ser o mesmo que foi informado quando você instanciou a class `PagSeguro::Order`. Por padrão, o status do pedido será `completed` e o tipo de pagamento `credit_card`. Você pode especificar esses parâmetros como no exemplo abaixo. 192 | 193 | $ rake pagseguro:notify ID=1 PAYMENT_METHOD=invoice STATUS=canceled NOTE="Enviar por motoboy" NAME="José da Silva" EMAIL="jose@dasilva.com" 194 | 195 | #### PAYMENT_METHOD 196 | 197 | * `credit_card`: Cartão de crédito 198 | * `invoice`: Boleto 199 | * `online_transfer`: Pagamento online 200 | * `pagseguro`: Transferência entre contas do PagSeguro 201 | 202 | #### STATUS 203 | 204 | * `completed`: Completo 205 | * `pending`: Aguardando pagamento 206 | * `approved`: Aprovado 207 | * `verifying`: Em análise 208 | * `canceled`: Cancelado 209 | * `refunded`: Devolvido 210 | 211 | ### Codificação (Encoding) 212 | 213 | Esta biblioteca assume que você está usando UTF-8 como codificação de seu projeto. Neste caso, o único ponto onde os dados são convertidos para UTF-8 é quando uma notificação é enviada do UOL em ISO-8859-1. 214 | 215 | Se você usa sua aplicação como ISO-8859-1, esta biblioteca NÃO IRÁ FUNCIONAR. Nenhum patch dando suporte ao ISO-8859-1 será aplicado; você sempre pode manter o seu próprio fork, caso precise. 216 | 217 | ## TROUBLESHOOTING 218 | 219 | **Quero utilizar o servidor em Python para testar o retorno automático, mas recebo OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B)** 220 | 221 | Neste caso, você precisa forçar a validação do POST enviado. Basta acrescentar a linha: 222 | 223 | ~~~.ruby 224 | pagseguro_notification do |notification| 225 | notification.valid?(:force => true) 226 | # resto do codigo... 227 | end 228 | ~~~ 229 | 230 | ## AUTOR: 231 | 232 | Nando Vieira () 233 | 234 | Recomendar no [Working With Rails](http://www.workingwithrails.com/person/7846-nando-vieira) 235 | 236 | ## COLABORADORES: 237 | 238 | * Elomar () 239 | * Rafael () 240 | 241 | ## LICENÇA: 242 | 243 | (The MIT License) 244 | 245 | Permission is hereby granted, free of charge, to any person obtaining 246 | a copy of this software and associated documentation files (the 247 | 'Software'), to deal in the Software without restriction, including 248 | without limitation the rights to use, copy, modify, merge, publish, 249 | distribute, sublicense, and/or sell copies of the Software, and to 250 | permit persons to whom the Software is furnished to do so, subject to 251 | the following conditions: 252 | 253 | The above copyright notice and this permission notice shall be 254 | included in all copies or substantial portions of the Software. 255 | 256 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 257 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 258 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 259 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 260 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 261 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 262 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 263 | --------------------------------------------------------------------------------