pagseguro 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>