pagseguro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +8 -0
  3. data/Gemfile.lock +104 -0
  4. data/README.markdown +212 -0
  5. data/Rakefile +25 -0
  6. data/lib/pagseguro.rb +66 -0
  7. data/lib/pagseguro/action_controller.rb +11 -0
  8. data/lib/pagseguro/developer_controller.rb +26 -0
  9. data/lib/pagseguro/generator.rb +12 -0
  10. data/lib/pagseguro/helper.rb +8 -0
  11. data/lib/pagseguro/notification.rb +192 -0
  12. data/lib/pagseguro/order.rb +73 -0
  13. data/lib/pagseguro/railtie.rb +23 -0
  14. data/lib/pagseguro/rake.rb +96 -0
  15. data/lib/pagseguro/routes.rb +4 -0
  16. data/lib/pagseguro/version.rb +8 -0
  17. data/lib/pagseguro/views/_form.html.erb +21 -0
  18. data/lib/tasks/pagseguro.rake +6 -0
  19. data/pagseguro.gemspec +95 -0
  20. data/spec/controllers/developer_controller_spec.rb +26 -0
  21. data/spec/helpers/helper_spec.rb +80 -0
  22. data/spec/pagseguro/notification_spec.rb +336 -0
  23. data/spec/pagseguro/order_spec.rb +79 -0
  24. data/spec/pagseguro/pagseguro_spec.rb +48 -0
  25. data/spec/pagseguro/rake_spec.rb +134 -0
  26. data/spec/spec_helper.rb +10 -0
  27. data/spec/support/app/controllers/application_controller.rb +2 -0
  28. data/spec/support/app/models/account.rb +2 -0
  29. data/spec/support/app/models/user.rb +3 -0
  30. data/spec/support/app/views/dashboard/index.erb +0 -0
  31. data/spec/support/app/views/session/new.erb +0 -0
  32. data/spec/support/config/boot.rb +14 -0
  33. data/spec/support/config/database.yml +3 -0
  34. data/spec/support/config/pagseguro.yml +12 -0
  35. data/spec/support/config/routes.rb +4 -0
  36. data/spec/support/log/development.log +0 -0
  37. data/spec/support/log/test.log +375 -0
  38. data/spec/support/matcher.rb +39 -0
  39. data/spec/support/pagseguro-test.yml +30 -0
  40. data/spec/support/tmp/pagseguro-test.yml +30 -0
  41. data/templates/config.yml +13 -0
  42. metadata +130 -0
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,8 @@
1
+ module PagSeguro::Helper
2
+ PAGSEGURO_FORM_VIEW = File.expand_path(File.dirname(__FILE__) + "/views/_form.html.erb")
3
+
4
+ def pagseguro_form(order, options = {})
5
+ options.reverse_merge!(:submit => "Pagar com PagSeguro")
6
+ render :file => PAGSEGURO_FORM_VIEW, :locals => {:options => options, :order => order}
7
+ end
8
+ end
@@ -0,0 +1,192 @@
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
+ MAPPING = {
8
+ :payment_method => "TipoPagamento",
9
+ :order_id => "Referencia",
10
+ :processed_at => "DataTransacao",
11
+ :status => "StatusTransacao",
12
+ :transaction_id => "TransacaoID",
13
+ :shipping_type => "TipoFrete",
14
+ :shipping => "ValorFrete",
15
+ :notes => "Anotacao"
16
+ }
17
+
18
+ # Map order status from PagSeguro
19
+ STATUS = {
20
+ "Completo" => :completed,
21
+ "Aguardando Pagto" => :pending,
22
+ "Aprovado" => :approved,
23
+ "Em Análise" => :verifying,
24
+ "Cancelado" => :canceled,
25
+ "Devolvido" => :refunded
26
+ }
27
+
28
+ # Map payment method from PagSeguro
29
+ PAYMENT_METHOD = {
30
+ "Cartão de Crédito" => :credit_card,
31
+ "Boleto" => :invoice,
32
+ "Pagamento" => :pagseguro,
33
+ "Pagamento online" => :online_transfer
34
+ }
35
+
36
+ # The Rails params hash
37
+ attr_accessor :params
38
+
39
+ # Expects the params object from the current request
40
+ def initialize(params, token = nil)
41
+ @token = token
42
+ @params = normalize(params)
43
+ end
44
+
45
+ # Normalize the specified hash converting all data to UTF-8
46
+ def normalize(hash)
47
+ each_value(hash) do |value|
48
+ value.to_s.unpack('C*').pack('U*')
49
+ end
50
+ end
51
+
52
+ # Denormalize the specified hash converting all data to ISO-8859-1
53
+ def denormalize(hash)
54
+ each_value(hash) do |value|
55
+ value.to_s.unpack('U*').pack('C*')
56
+ end
57
+ end
58
+
59
+ # Return a list of products sent by PagSeguro.
60
+ # The values will be normalized
61
+ # (e.g. currencies will be converted to cents, quantity will be an integer)
62
+ def products
63
+ @products ||= begin
64
+ items = []
65
+
66
+ for i in (1..params["NumItens"].to_i)
67
+ items << {
68
+ :id => params["ProdID_#{i}"],
69
+ :description => params["ProdDescricao_#{i}"],
70
+ :quantity => params["ProdQuantidade_#{i}"].to_i,
71
+ :price => to_price(params["ProdValor_#{i}"]),
72
+ :shipping => to_price(params["ProdFrete_#{i}"]),
73
+ :fees => to_price(params["ProdExtras_#{i}"])
74
+ }
75
+ end
76
+
77
+ items
78
+ end
79
+ end
80
+
81
+ # Return the shipping fee
82
+ # Will be converted to a float number
83
+ def shipping
84
+ to_price mapping_for(:shipping)
85
+ end
86
+
87
+ # Return the order status
88
+ # Will be mapped to the STATUS constant
89
+ def status
90
+ @status ||= STATUS[mapping_for(:status)]
91
+ end
92
+
93
+ # Return the payment method
94
+ # Will be mapped to the PAYMENT_METHOD constant
95
+ def payment_method
96
+ @payment_method ||= PAYMENT_METHOD[mapping_for(:payment_method)]
97
+ end
98
+
99
+ # Parse the processing date to a Ruby object
100
+ def processed_at
101
+ @processed_at ||= begin
102
+ groups = *mapping_for(:processed_at).match(/(\d{2})\/(\d{2})\/(\d{4}) ([\d:]+)/sm)
103
+ Time.parse("#{groups[3]}-#{groups[2]}-#{groups[1]} #{groups[4]}")
104
+ end
105
+ end
106
+
107
+ # Return the buyer info
108
+ def buyer
109
+ @buyer ||= {
110
+ :name => params["CliNome"],
111
+ :email => params["CliEmail"],
112
+ :phone => {
113
+ :area_code => params["CliTelefone"].to_s.split(" ").first,
114
+ :number => params["CliTelefone"].to_s.split(" ").last
115
+ },
116
+ :address => {
117
+ :street => params["CliEndereco"],
118
+ :number => params["CliNumero"],
119
+ :complements => params["CliComplemento"],
120
+ :neighbourhood => params["CliBairro"],
121
+ :city => params["CliCidade"],
122
+ :state => params["CliEstado"],
123
+ :postal_code => params["CliCEP"]
124
+ }
125
+ }
126
+ end
127
+
128
+ def method_missing(method, *args)
129
+ return mapping_for(method) if MAPPING[method]
130
+ super
131
+ end
132
+
133
+ # A wrapper to the params hash,
134
+ # sanitizing the return to symbols
135
+ def mapping_for(name)
136
+ params[MAPPING[name]]
137
+ end
138
+
139
+ # Cache the validation.
140
+ # To bypass the cache, just provide an argument that is evaluated as true.
141
+ #
142
+ # invoice.valid?
143
+ # invoice.valid?(:nocache)
144
+ def valid?(force=false)
145
+ @valid = nil if force
146
+ @valid = validates? if @valid.nil?
147
+ @valid
148
+ end
149
+
150
+ private
151
+ def each_value(hash, &blk)
152
+ hash.each do |key, value|
153
+ if value.kind_of?(Hash)
154
+ hash[key] = each_value(value, &blk)
155
+ else
156
+ hash[key] = blk.call value
157
+ end
158
+ end
159
+
160
+ hash
161
+ end
162
+
163
+ # Convert amount format to float
164
+ def to_price(amount)
165
+ amount.to_s.gsub(/[^\d]/, "").gsub(/^(\d+)(\d{2})$/, '\1.\2').to_f
166
+ end
167
+
168
+ # Check if the provided data is valid by requesting the
169
+ # confirmation API url. The request will always be valid while running in
170
+ # developer mode.
171
+ def validates?
172
+ return true if PagSeguro.developer?
173
+
174
+ # include the params to validate our request
175
+ request_params = denormalize params.merge({
176
+ :Comando => "validar",
177
+ :Token => @token || PagSeguro.config["authenticity_token"]
178
+ }).dup
179
+
180
+ # do the request
181
+ uri = URI.parse(API_URL)
182
+ http = Net::HTTP.new(uri.host, uri.port)
183
+ http.use_ssl = true
184
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
185
+
186
+ request = Net::HTTP::Post.new(uri.path)
187
+ request.set_form_data request_params
188
+ response = http.start {|r| r.request request }
189
+ (response.body =~ /VERIFICADO/) != nil
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,73 @@
1
+ module PagSeguro
2
+ class Order
3
+ # The list of products added to the order
4
+ attr_accessor :products
5
+
6
+ # Define the shipping type.
7
+ # Can be EN (PAC) or SD (Sedex)
8
+ attr_accessor :shipping_type
9
+
10
+ def initialize(order_id = nil)
11
+ reset!
12
+ self.id = order_id
13
+ end
14
+
15
+ # Set the order identifier. Should be a unique
16
+ # value to identify this order on your own application
17
+ def id=(identifier)
18
+ @id = identifier
19
+ end
20
+
21
+ # Get the order identifier
22
+ def id
23
+ @id
24
+ end
25
+
26
+ # Remove all products from this order
27
+ def reset!
28
+ @products = []
29
+ end
30
+
31
+ # Add a new product to the PagSeguro order
32
+ # The allowed values are:
33
+ # - weight (Optional. If float, will be multiplied by 1000g)
34
+ # - shipping (Optional. If float, will be multiplied by 100 cents)
35
+ # - quantity (Optional. Defaults to 1)
36
+ # - price (Required. If float, will be multiplied by 100 cents)
37
+ # - description (Required. Identifies the product)
38
+ # - id (Required. Should match the product on your database)
39
+ # - fees (Optional. If float, will be multiplied by 100 cents)
40
+ def <<(options)
41
+ options = {
42
+ :weight => nil,
43
+ :shipping => nil,
44
+ :fees => nil,
45
+ :quantity => 1
46
+ }.merge(options)
47
+
48
+ # convert shipping to cents
49
+ options[:shipping] = convert_unit(options[:shipping], 100)
50
+
51
+ # convert fees to cents
52
+ options[:fees] = convert_unit(options[:fees], 100)
53
+
54
+ # convert price to cents
55
+ options[:price] = convert_unit(options[:price], 100)
56
+
57
+ # convert weight to grammes
58
+ options[:weight] = convert_unit(options[:weight], 1000)
59
+
60
+ products.push(options)
61
+ end
62
+
63
+ def add(options)
64
+ self << options
65
+ end
66
+
67
+ private
68
+ def convert_unit(number, unit)
69
+ number = (number * unit).to_i unless number.nil? || number.kind_of?(Integer)
70
+ number
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,23 @@
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
+ require "pagseguro/developer_controller"
10
+ app.routes_reloader.paths.unshift(File.dirname(__FILE__) + "/routes.rb")
11
+ end
12
+ end
13
+
14
+ rake_tasks do
15
+ load File.dirname(__FILE__) + "/../tasks/pagseguro.rake"
16
+ end
17
+
18
+ initializer "pagseguro.initialize" do |app|
19
+ ::ActionView::Base.send(:include, PagSeguro::Helper)
20
+ ::ActionController::Base.send(:include, PagSeguro::ActionController)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,96 @@
1
+ module PagSeguro
2
+ module Rake
3
+ extend self
4
+
5
+ def run
6
+ require "digest/md5"
7
+ require "faker"
8
+
9
+ # Not running in developer mode? Exit!
10
+ unless PagSeguro.developer?
11
+ puts "=> [PagSeguro] Can only notify development URLs"
12
+ puts "=> [PagSeguro] Double check your config/pagseguro.yml file"
13
+ exit 1
14
+ end
15
+
16
+ # There's no orders file! Exit!
17
+ unless File.exist?(PagSeguro::DeveloperController::PAGSEGURO_ORDERS_FILE)
18
+ puts "=> [PagSeguro] No orders added. Exiting now!"
19
+ exit 1
20
+ end
21
+
22
+ # Load the orders file
23
+ orders = YAML.load_file(PagSeguro::DeveloperController::PAGSEGURO_ORDERS_FILE)
24
+
25
+ # Ops! No orders added! Exit!
26
+ unless orders
27
+ puts "=> [PagSeguro] No invoices created. Exiting now!"
28
+ exit 1
29
+ end
30
+
31
+ # Get the specified order
32
+ order = orders[ENV["ID"]]
33
+
34
+ # Not again! No order! Exit!
35
+ unless order
36
+ puts "=> [PagSeguro] The order #{ENV['ID'].inspect} could not be found. Exiting now!"
37
+ exit 1
38
+ end
39
+
40
+ # Set the client's info
41
+ order["CliNome"] = ENV["NAME"] || Faker::Name.name
42
+ order["CliEmail"] = ENV["EMAIL"] || Faker::Internet.email
43
+ order["CliEndereco"] = Faker::Address.street_name
44
+ order["CliNumero"] = rand(1000)
45
+ order["CliComplemento"] = Faker::Address.secondary_address
46
+ order["CliBairro"] = Faker::Address.city
47
+ order["CliCidade"] = Faker::Address.city
48
+ order["CliCEP"] = "12345678"
49
+ order["CliTelefone"] = "11 12345678"
50
+
51
+ # Set the transaction date
52
+ order["DataTransacao"] = Time.now.strftime("%d/%m/%Y %H:%M:%S")
53
+
54
+ # Replace the order id to the correct name
55
+ order["Referencia"] = order.delete("ref_transacao")
56
+
57
+ # Count the number of products in this order
58
+ order["NumItens"] = order.inject(0) do |count, (key, value)|
59
+ count += 1 if key =~ /item_id_/
60
+ count
61
+ end
62
+
63
+ # Replace all products
64
+ to_price = lambda {|s| s.gsub(/^(.*?)(.{2})$/, '\1,\2') }
65
+
66
+ for index in (1..order["NumItens"])
67
+ order["ProdID_#{index}"] = order.delete("item_id_#{index}")
68
+ order["ProdDescricao_#{index}"] = order.delete("item_descr_#{index}")
69
+ order["ProdValor_#{index}"] = to_price.call(order.delete("item_valor_#{index}"))
70
+ order["ProdQuantidade_#{index}"] = order.delete("item_quant_#{index}")
71
+ order["ProdFrete_#{index}"] = order["item_frete_#{index}"] == "0" ? "0,00" : to_price.call(order.delete("item_frete_#{index}"))
72
+ order["ProdExtras_#{index}"] = "0,00"
73
+ end
74
+
75
+ # Retrieve the specified status or default to :completed
76
+ status = (ENV["STATUS"] || :completed).to_sym
77
+
78
+ # Retrieve the specified payment method or default to :credit_card
79
+ payment_method = (ENV["PAYMENT_METHOD"] || :credit_card).to_sym
80
+
81
+ # Set a random transaction id
82
+ order["TransacaoID"] = Digest::MD5.hexdigest(Time.now.to_s)
83
+
84
+ # Set note
85
+ order["Anotacao"] = ENV["NOTE"].to_s
86
+
87
+ # Set payment method and status
88
+ order["TipoPagamento"] = PagSeguro::Notification::PAYMENT_METHOD.key(payment_method)
89
+ order["StatusTransacao"] = PagSeguro::Notification::STATUS.key(status)
90
+
91
+ # Finally, ping the configured return URL
92
+ uri = URI.parse File.join(PagSeguro.config["base"], PagSeguro.config["return_to"])
93
+ Net::HTTP.post_form uri, order
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,4 @@
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
@@ -0,0 +1,8 @@
1
+ module PagSeguro
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ <form accept-charset="ISO-8859-1" action="<%= PagSeguro.gateway_url %>" id="pagseguro" class="pagseguro" method="post">
2
+ <div>
3
+ <%= hidden_field_tag "email_cobranca", options[:email] || PagSeguro.config["email"] %>
4
+ <%= hidden_field_tag "tipo", "CP" %>
5
+ <%= hidden_field_tag "moeda", "BRL" %>
6
+ <%= hidden_field_tag "ref_transacao", order.id %>
7
+ <%= hidden_field_tag "tipo_frete", order.shipping_type if order.shipping_type %>
8
+
9
+ <% order.products.each_with_index do |product, i| %>
10
+ <% i += 1 %>
11
+ <%= hidden_field_tag "item_quant_#{i}", product[:quantity] %>
12
+ <%= hidden_field_tag "item_id_#{i}", product[:id] %>
13
+ <%= hidden_field_tag "item_descr_#{i}", product[:description] %>
14
+ <%= hidden_field_tag "item_valor_#{i}", product[:price] %>
15
+ <%= hidden_field_tag "item_peso_#{i}", product[:weight].to_i if product[:weight] %>
16
+ <%= hidden_field_tag "item_frete_#{i}", product[:shipping].to_i if product[:shipping] %>
17
+ <% end %>
18
+
19
+ <%= submit_tag options[:submit] %>
20
+ </div>
21
+ </form>