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.
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +104 -0
- data/README.markdown +212 -0
- data/Rakefile +25 -0
- data/lib/pagseguro.rb +66 -0
- data/lib/pagseguro/action_controller.rb +11 -0
- data/lib/pagseguro/developer_controller.rb +26 -0
- data/lib/pagseguro/generator.rb +12 -0
- data/lib/pagseguro/helper.rb +8 -0
- data/lib/pagseguro/notification.rb +192 -0
- data/lib/pagseguro/order.rb +73 -0
- data/lib/pagseguro/railtie.rb +23 -0
- data/lib/pagseguro/rake.rb +96 -0
- data/lib/pagseguro/routes.rb +4 -0
- data/lib/pagseguro/version.rb +8 -0
- data/lib/pagseguro/views/_form.html.erb +21 -0
- data/lib/tasks/pagseguro.rake +6 -0
- data/pagseguro.gemspec +95 -0
- data/spec/controllers/developer_controller_spec.rb +26 -0
- data/spec/helpers/helper_spec.rb +80 -0
- data/spec/pagseguro/notification_spec.rb +336 -0
- data/spec/pagseguro/order_spec.rb +79 -0
- data/spec/pagseguro/pagseguro_spec.rb +48 -0
- data/spec/pagseguro/rake_spec.rb +134 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/app/controllers/application_controller.rb +2 -0
- data/spec/support/app/models/account.rb +2 -0
- data/spec/support/app/models/user.rb +3 -0
- data/spec/support/app/views/dashboard/index.erb +0 -0
- data/spec/support/app/views/session/new.erb +0 -0
- data/spec/support/config/boot.rb +14 -0
- data/spec/support/config/database.yml +3 -0
- data/spec/support/config/pagseguro.yml +12 -0
- data/spec/support/config/routes.rb +4 -0
- data/spec/support/log/development.log +0 -0
- data/spec/support/log/test.log +375 -0
- data/spec/support/matcher.rb +39 -0
- data/spec/support/pagseguro-test.yml +30 -0
- data/spec/support/tmp/pagseguro-test.yml +30 -0
- data/templates/config.yml +13 -0
- 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,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>
|