pag_seguro 0.4.1 → 0.5.1

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 (36) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +5 -2
  3. data/README.md +71 -6
  4. data/lib/pag_seguro/checkout.xml.haml +24 -5
  5. data/lib/pag_seguro/convert_field_to_digit.rb +15 -0
  6. data/lib/pag_seguro/day_of_year.rb +35 -0
  7. data/lib/pag_seguro/item.rb +9 -11
  8. data/lib/pag_seguro/notification.rb +1 -1
  9. data/lib/pag_seguro/payment.rb +26 -19
  10. data/lib/pag_seguro/pre_approval.rb +84 -0
  11. data/lib/pag_seguro/query.rb +27 -1
  12. data/lib/pag_seguro/sender.rb +2 -2
  13. data/lib/pag_seguro/shipping.rb +2 -2
  14. data/lib/pag_seguro/transaction.rb +1 -1
  15. data/lib/pag_seguro/version.rb +1 -1
  16. data/lib/pag_seguro.rb +9 -1
  17. data/lib/pagseguro_decimal_validator.rb +9 -0
  18. data/pag_seguro.gemspec +1 -0
  19. data/spec/factories.rb +137 -0
  20. data/spec/fixtures/transaction_history.xml +40 -0
  21. data/spec/pag_seguro/checkout_xml_spec.rb +142 -159
  22. data/spec/pag_seguro/convert_field_to_digit_spec.rb +68 -0
  23. data/spec/pag_seguro/day_of_year_spec.rb +49 -0
  24. data/spec/pag_seguro/integration/checkout_spec.rb +34 -67
  25. data/spec/pag_seguro/integration/config.yml +4 -4
  26. data/spec/pag_seguro/integration/query_spec.rb +56 -34
  27. data/spec/pag_seguro/item_spec.rb +46 -72
  28. data/spec/pag_seguro/payment_method_spec.rb +58 -63
  29. data/spec/pag_seguro/payment_spec.rb +150 -123
  30. data/spec/pag_seguro/pre_approval_spec.rb +112 -0
  31. data/spec/pag_seguro/query_spec.rb +111 -4
  32. data/spec/pag_seguro/sender_spec.rb +50 -62
  33. data/spec/pag_seguro/shipping_spec.rb +36 -51
  34. data/spec/spec_helper.rb +11 -20
  35. data/spec/support/transaction_shared_examples.rb +7 -7
  36. metadata +32 -3
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ coverage/*
6
6
  doc/*
7
7
  spec/pag_seguro/integration/config_example.yml
8
8
  .DS_Store
9
+ .sublime-project
data/Gemfile CHANGED
@@ -3,8 +3,11 @@ source "http://rubygems.org"
3
3
  # Specify your gem's dependencies in PagSeguro.gemspec
4
4
  gemspec
5
5
 
6
- # test gems
6
+ # test or development gems
7
7
  gem 'rspec'
8
8
  gem 'simplecov', require: false
9
9
  gem 'guard-rspec'
10
- gem 'growl'
10
+ gem 'shoulda-matchers'
11
+ gem "factory_girl", "~> 4.0"
12
+ gem 'growl'
13
+ gem 'pry'
data/README.md CHANGED
@@ -73,7 +73,9 @@ Com exceção do atributo response (que é utilizado para armazenar a resposta e
73
73
 
74
74
  ### API de Notificação
75
75
 
76
- As notificações de alteração no status da compra no PagSeguro serão enviadas para a URL que tiver configurado na [Notificação de transações](https://pagseguro.uol.com.br/v2/guia-de-integracao/consulta-de-transacoes-por-codigo.html). Obs.: Até o momento o PagSeguro não permite configurar uma url dinâmica para envio das notificação ( e apenas permite uma url por conta ), então provavelemente será necessário que crie uma conta diferente no PagSeguro para cada sistema que desenvolver.
76
+ As notificações de alteração no status da compra no PagSeguro serão enviadas para a URL que tiver configurado na [Notificação de transações](https://pagseguro.uol.com.br/v2/guia-de-integracao/consulta-de-transacoes-por-codigo.html). Se quiser configurar uma url dinâmica para envio das notificação é necessário ativar a página de redirecionamento dinâmico em [Integrações > Página de redirecionamento](https://pagseguro.uol.com.br/integracao/pagina-de-redirecionamento.jhtml), e passar o argumento `redirect_url` para o objeto PagSeguro::Payment:
77
+
78
+ PagSeguro::Payment.new(email, token, id: invoice.id, redirect_url: "http://lojamodelo.com.br/checkout")
77
79
 
78
80
  O código da notificação é enviado pelo PagSeguro através do parâmentro `notificationCode` em uma requisição do tipo POST. Segue um exemplo de uso da notificação em uma aplicação rails (este exemplo supõe a existência de um `resources :notifications` em suas rotas, e um modelo `Invoice` responsável pelos pagamentos):
79
81
 
@@ -108,15 +110,28 @@ Para este exemplo, o url configurada na [Notificação de transações](https://
108
110
 
109
111
  ### Consulta de Transações
110
112
 
111
- Para realizar a consulta de uma transação é preciso obter o código da transação. Este código é enviado nas Notificações de Transações do PagSeguro (de forma assíncrona), através do método `notification.transaction_id` ou de forma síncrona assim que o usuário retorna à loja após ter concluído a compra.
113
+ duas maneiras de se realizar a consulta das transações, a primeira delas buscando o código de uma transação, e outra buscando por todas as transações em um determinado período
112
114
 
113
- Para buscar informações da transação de forma síncrona, é necessário que acesse sua conta no PagSeguro, e clique em [Integrações > Página de redirecionamento](https://pagseguro.uol.com.br/integracao/pagina-de-redirecionamento.jhtml) e ative o redirecionamento com o código da transação, definindo o nome do parâmetro que será enviado para sua aplicação (e.g.: http://lojamodelo.com.br/checkout?transaction_id=E884542-81B3-4419-9A75-BCC6FB495EF1 ). O redirecionamento para esta página é executado através de uma requisição GET.
115
+ #### Consulta de Transações por Período
114
116
 
115
- Caso queira utilizar uma URL dinâmica de retorno, é necessário ativar a página de redirecionamento dinâmico em [Integrações > Página de redirecionamento](https://pagseguro.uol.com.br/integracao/pagina-de-redirecionamento.jhtml), e passar o argumento `redirect_url` para o objeto PagSeguro::Payment:
117
+ Você pode consultar as transações por uma data através do método `PagSeguro::Query::find`, informando seu email, token, e algumas opções adicionais:
116
118
 
117
- PagSeguro::Payment.new(email, token, id: invoice.id, redirect_url: "http://lojamodelo.com.br/checkout")
119
+ transactions = PagSeguro::Query.find(email, token, initial_date: 30.days.ago, final_date: Time.now)
120
+
121
+ transactions.each do |transaction|
122
+ puts "id: #{transaction.id}, transaction_id: #{transaction.transaction_id}"
123
+ ...
124
+ end
125
+
126
+ Além das opções acima, você pode enviar também as opções `:page` (que pelo padrão é a primeira) e `:max_page_results` (que por padrão é 50). Obviamente a data final precisa ser maior que a inicial, não podem haver mais de 30 dias de diferença entre as duas, e a data inicial precisa estar dentro dos últimos 6 meses.
127
+
128
+ #### Consulta de Transação por código
118
129
 
119
- Você pode consultar as informações da transação através do `PagSeguro::Query`, que possui os mesmos attributos e métodos que `PagSeguro::Notification` para consulta da transação:
130
+ O código das transações são enviadas nas Notificações de Transações do PagSeguro (de forma assíncrona), e podem ser obtidas através do método `notification.transaction_id`, e também podem ser obtidas de forma síncrona assim que o usuário retorna à loja após ter concluído a compra. Este código também pode ser encontrado através da busca de transações por período.
131
+
132
+ Para buscar informações da transação de forma síncrona, é necessário que acesse sua conta no PagSeguro, e clique em [Integrações > Página de redirecionamento](https://pagseguro.uol.com.br/integracao/pagina-de-redirecionamento.jhtml) e ative o redirecionamento com o código da transação, definindo o nome do parâmetro que será enviado para sua aplicação (e.g.: http://lojamodelo.com.br/checkout?transaction_id=E884542-81B3-4419-9A75-BCC6FB495EF1 ). O redirecionamento para esta página é executado através de uma requisição GET.
133
+
134
+ Você pode consultar as informações da transação instanciando a classe `PagSeguro::Query`, que possui os mesmos attributos e métodos que uma notificação:
120
135
 
121
136
  query = PagSeguro::Query.new(email, token, "E884542-81B3-4419-9A75-BCC6FB495EF1")
122
137
 
@@ -124,6 +139,54 @@ Você pode consultar as informações da transação através do `PagSeguro::Que
124
139
  # ...
125
140
  end
126
141
 
142
+ ### Pagamento Recorrente
143
+
144
+ **Primeiro de tudo vale ressaltar que a API de pagamento recorrente não está documentada oficialmente pelo pagseguro, apesar de ter sido lançada em dezembro de 2012. Esta funcionalidade foi criada com base em um post em um [blog](http://sounoob.com.br/requisicao-de-pagamento-do-pagseguro-com-assinatura-associada-usando-php/) e em tentativa e erro. Use por sua conta e risco.**
145
+
146
+ É possível enviar a requisição de uma pagamento recorrente juntamente com o pedido de compra (e por enquanto não é possível enviar um pedido de assinatura sem enviar adicionar nenhum ítem ao pagamento). Para usá-la, basta adicionar um `pre_approval` a um pagamento:
147
+
148
+ # suponho que uma variavel payment (do tipo PagSeguro::Payment) já foi instanciada, e que o payment.items não está vazio
149
+ payment.pre_approval = PagSeguro::PreApproval.new
150
+
151
+ # obrigatório. Recebe uma string (de até 100 caracteres) e representa o nome da sua assinatura
152
+ payment.pre_approval.name = "nome da minha assinatura"
153
+
154
+ # obrigatório. Recebe uma data e representa a data em que sua assinatura termina. Não pode ser maior do que a data de início (ou hoje) em mais de 744 dias (pouco menos de 3 anos)
155
+ payment.pre_approval.pre_approval.final_date = Date.new(2014, 6, 12)
156
+
157
+ # obrigatório. Valor máximo da assinatura por período/cobrança. Recebe uma string (formatada como "%.2f"), um float ou um BigDecimal
158
+ payment.pre_approval.max_amount_per_period = '200.00'
159
+
160
+ # obrigatório. Valor máximo total da assinatura. Recebe uma string (formatada como "%.2f"), um float ou um BigDecimal
161
+ payment.pre_approval.max_total_amount = '1000.00'
162
+
163
+ # obrigatório. Representa a periodicidade da cobraça. Recebe uma string ou símbolo e pode ser: weekly, monthly, bimonthly, trimonthly, semiannually, ou yearly
164
+ payment.pre_approval.period = :monthly
165
+
166
+ # obrigatório no caso de pagamentos de periodicidade monthly, bimonthly ou trimonthly. Recebe um número (dia do mês) de 1 à 28
167
+ payment.pre_approval.day_of_month = 10
168
+
169
+ # obrigatório no caso de pagamentos de periodicidade weekly. Recebe uma string ou símbolo representando o dia na semana, e pode ser monday, tuesday, wednesday, thursday, friday, saturday ou sunday
170
+ payment.pre_approval.day_of_week = :friday
171
+
172
+ # obrigatório no caso de pagamentos de periodicidade yearly. Recebe uma string representando o dia do mês e o mês do ano no formato 'MM-dd'. Para facilitar use a classe DayOfYear que gera a string no formato correto.
173
+ payment.pre_approval.day_of_year = PagSeguro::DayOfYear.new(day: 10, month: 4)
174
+
175
+ # estranhamente é opcional! Valor de cada cobrança. Recebe uma string (no formato "%.2f"), um float ou um BigDecimal
176
+ payment.pre_approval.amount_per_payment = '200.00'
177
+
178
+ # opcional. Recebe uma string (de até 255 caracteres) e representa os detalhes da assinatura
179
+ payment.pre_approval.details = "detalhes da assinatura"
180
+
181
+ # opcional. Recebe uma data de quando a assinatura passa a valer. Não pode ser maior do que 2 anos da data atual, e precisa ser inferior a data de final_date (que pode ser maior em até 744 dias da data de início)
182
+ payment.pre_approval.initial_date = Date.new(2014, 3, 12)
183
+
184
+ # opcional. Recebe uma string que supostamente deveria levar às condições da sua assinatura
185
+ payment.pre_approval.reviewURL = "http://seuproduto.com/assinatura"
186
+
187
+ # Por fim gere a URL do pagseguro da mesma forma como nas compras/pagamentos normais.
188
+ redirect_to_url = payment.checkout_payment_url
189
+
127
190
  ## Validações
128
191
 
129
192
  Os modelos utilizados nesta gem utilizam as validações do ActiveModel (semelhantes às presentes em ActiveRecord/Rails) e incluem diversas validações, permitindo que se verifique a validade (utilizando object.valid?) dos dados antes de enviá-los ao PagSeguro. A gem não bloqueia o envio das informações caso os dados estejam inválidos, deixando este passo a cargo da sua aplicação, mas levanta erros caso o pag seguro retorne algum erro relativo às informações enviadas.
@@ -152,3 +215,5 @@ Desenvolvida por [Stefano Diem Benatti](mailto:stefano@heavenstudio.com.br)
152
215
  Rafael Castilho (<http://github.com/castilhor>)
153
216
 
154
217
  Rafael Ivan Garcia (https://github.com/rafaelivan)
218
+
219
+ efmiglioranza (https://github.com/efmiglioranza)
@@ -8,7 +8,7 @@
8
8
  %redirectURL= payment.redirect_url
9
9
  - if payment.max_uses.present?
10
10
  %maxUses= payment.max_uses
11
- - if payment. max_age.present?
11
+ - if payment.max_age.present?
12
12
  %maxAge= payment.max_age
13
13
 
14
14
  %currency BRL
@@ -39,10 +39,7 @@
39
39
 
40
40
  - if shipping.present?
41
41
  %shipping
42
- - if !shipping.type.zero?
43
- %type= shipping.type
44
- - else
45
- %type 3
42
+ %type= shipping.type
46
43
  - if shipping.cost.present?
47
44
  %cost= shipping.cost
48
45
  %address
@@ -61,3 +58,25 @@
61
58
  %number= shipping.number
62
59
  - if shipping.complement.present?
63
60
  %complement= shipping.complement
61
+
62
+ - if pre_approval.present?
63
+ %preApproval
64
+ %name= pre_approval.name
65
+ %finalDate= pre_approval.final_date.iso8601
66
+ %maxAmountPerPeriod= pre_approval.max_amount_per_period
67
+ %maxTotalAmount= pre_approval.max_total_amount
68
+ %period= pre_approval.period
69
+ - if pre_approval.weekly?
70
+ %dayOfWeek= pre_approval.day_of_week
71
+ - if pre_approval.monthly?
72
+ %dayOfMonth= pre_approval.day_of_month
73
+ - if pre_approval.yearly?
74
+ %dayOfYear= pre_approval.day_of_year
75
+ - if pre_approval.details.present?
76
+ %details= pre_approval.details
77
+ - if pre_approval.amount_per_payment.present?
78
+ %amountPerPayment= pre_approval.amount_per_payment
79
+ - if pre_approval.initial_date.present?
80
+ %initialDate= pre_approval.initial_date.iso8601
81
+ - if pre_approval.review_URL.present?
82
+ %reviewURL= pre_approval.review_URL
@@ -0,0 +1,15 @@
1
+ module PagSeguro
2
+ module ConvertFieldToDigit
3
+ def attr_reader_as_digit(*fields)
4
+ fields.each do |field|
5
+ define_method(field) do
6
+ begin
7
+ "%.2f" % instance_variable_get("@#{field}")
8
+ rescue ArgumentError, TypeError
9
+ instance_variable_get("@#{field}")
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ module PagSeguro
2
+ module Error
3
+ class InvalidDayOfYear < Exception
4
+ def initialize(date_of_year)
5
+ super("DateOfYear should be a valid date: (month: #{date_of_year.month}, day: #{date_of_year.day})")
6
+ end
7
+ end
8
+ end
9
+
10
+ class DayOfYear
11
+ include Comparable
12
+ attr_accessor :day, :month
13
+
14
+ def initialize(options = {})
15
+ @day = options[:day]
16
+ @month = options[:month]
17
+ end
18
+
19
+ def to_s
20
+ raise Error::InvalidDayOfYear.new(self) unless valid?
21
+ "#{"%02d" % @month}-#{"%02d" % @day}"
22
+ end
23
+
24
+ def valid?
25
+ # very simple date validation, just to smoke test possible errors of switching day with month
26
+ @day < 31 && @month < 12
27
+ end
28
+
29
+ def <=>(other_day_of_the_year)
30
+ return 1 if @month > other_day_of_the_year.month
31
+ return -1 if @month < other_day_of_the_year.month
32
+ @day <=> other_day_of_the_year.day
33
+ end
34
+ end
35
+ end
@@ -1,14 +1,17 @@
1
1
  module PagSeguro
2
2
  class Item
3
3
  include ActiveModel::Validations
4
+ extend PagSeguro::ConvertFieldToDigit
4
5
 
5
6
  attr_accessor :id, :description, :amount, :quantity, :shipping_cost, :weight
7
+ attr_reader_as_digit :amount, :shipping_cost
6
8
 
7
- validates_presence_of :id, :description, :amount, :quantity
8
- validates_format_of :amount, with: /^\d+\.\d{2}$/, message: " must be a decimal and have 2 digits after the dot"
9
- validates_format_of :shipping_cost, with: /^\d+\.\d{2}$/, message: " must be a decimal and have 2 digits after the dot"
10
- validates_format_of :weight, with: /^\d+$/, message: " must be an integer"
11
- validate :quantity_amount
9
+ validates :id, presence: true
10
+ validates :description, presence: true
11
+ validates :amount, pagseguro_decimal: true, presence: true
12
+ validates :shipping_cost, pagseguro_decimal: true
13
+ validates :weight, numericality: { only_integer: true, greater_than: 0, allow_blank: true }
14
+ validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0, less_than: 1000 }
12
15
 
13
16
  def initialize(attributes = {})
14
17
  @id = attributes[:id]
@@ -22,10 +25,5 @@ module PagSeguro
22
25
  def description
23
26
  @description.present? && @description.size > 100 ? @description[0..99] : @description
24
27
  end
25
-
26
- protected
27
- def quantity_amount
28
- errors.add(:quantity, " must be a number between 1 and 999") if @quantity.present? && (@quantity == "0" || @quantity.to_s !~ /^\d{1,3}$/)
29
- end
30
- end
28
+ end
31
29
  end
@@ -10,7 +10,7 @@ module PagSeguro
10
10
 
11
11
  private
12
12
  def transaction_data(email, token, notification_code)
13
- super(RestClient.get("#{PAGSEGURO_TRANSACTIONS_URL}/notifications/#{notification_code}?email=#{email}&token=#{token}"))
13
+ super RestClient.get "#{PAGSEGURO_TRANSACTIONS_URL}/notifications/#{notification_code}", params: {email: email, token: token}
14
14
  end
15
15
  end
16
16
  end
@@ -1,13 +1,16 @@
1
1
  module PagSeguro
2
2
  class Payment
3
3
  include ActiveModel::Validations
4
-
5
- attr_accessor :id, :email, :token, :items, :sender, :shipping, :extra_amount, :redirect_url, :max_uses, :max_age, :response
4
+ extend PagSeguro::ConvertFieldToDigit
5
+
6
+ attr_accessor :id, :email, :token, :items, :sender, :shipping, :extra_amount,
7
+ :redirect_url, :max_uses, :max_age, :response, :pre_approval
8
+ attr_reader_as_digit :extra_amount
6
9
 
7
10
  validates_presence_of :email, :token
8
- validates_format_of :extra_amount, with: /^\d+\.\d{2}$/, message: " must be a decimal and have 2 digits after the dot", allow_blank: true
11
+ validates :extra_amount, pagseguro_decimal: true
9
12
  validates_format_of :redirect_url, with: URI::regexp(%w(http https)), message: " must give a correct url for redirection", allow_blank: true
10
- validate :max_uses_number, :max_age_number
13
+ validate :max_uses_number, :max_age_number, :valid_pre_approval, :valid_items
11
14
 
12
15
  def initialize(email = nil, token = nil, options = {})
13
16
  @email = email unless email.nil?
@@ -20,6 +23,7 @@ module PagSeguro
20
23
  @redirect_url = options[:redirect_url]
21
24
  @max_uses = options[:max_uses]
22
25
  @max_age = options[:max_age]
26
+ @pre_approval = options[:pre_approval]
23
27
  end
24
28
 
25
29
  def self.checkout_payment_url(code)
@@ -28,7 +32,7 @@ module PagSeguro
28
32
 
29
33
  def checkout_xml
30
34
  xml_content = File.open( File.dirname(__FILE__) + "/checkout.xml.haml" ).read
31
- Haml::Engine.new(xml_content).render(nil, items: @items, payment: self, sender: @sender, shipping: @shipping)
35
+ Haml::Engine.new(xml_content).render(nil, items: @items, payment: self, sender: @sender, shipping: @shipping, pre_approval: @pre_approval)
32
36
  end
33
37
 
34
38
  def checkout_url_with_params
@@ -40,12 +44,12 @@ module PagSeguro
40
44
  end
41
45
 
42
46
  def code
43
- @response ||= parse_checkout_response
47
+ response || parse_checkout_response
44
48
  parse_code
45
49
  end
46
50
 
47
51
  def date
48
- @response ||= parse_checkout_response
52
+ response || parse_checkout_response
49
53
  parse_date
50
54
  end
51
55
 
@@ -61,30 +65,33 @@ module PagSeguro
61
65
  def max_age_number
62
66
  errors.add(:max_age, " must be an integer grater or equal to 30") if @max_age.present? && @max_age.to_i < 30
63
67
  end
68
+
69
+ def valid_pre_approval
70
+ errors.add(:pre_approval, " must be valid") if pre_approval && !pre_approval.valid?
71
+ end
72
+
73
+ def valid_items
74
+ errors.add(:items, " must be all valid") if items.blank? || !items.all?(&:valid?)
75
+ end
64
76
 
65
77
  def send_checkout
66
78
  RestClient.post(checkout_url_with_params, checkout_xml, content_type: "application/xml"){|resp, request, result| resp }
67
79
  end
68
80
 
69
81
  def parse_checkout_response
70
- response = send_checkout
71
- if response.code == 200
72
- response.body
73
- elsif response.code == 401
74
- raise Errors::Unauthorized
75
- elsif response.code == 400
76
- raise Errors::InvalidData.new(response.body)
77
- else
78
- raise Errors::UnknownError.new(response)
79
- end
82
+ res = send_checkout
83
+ raise Errors::Unauthorized if res.code == 401
84
+ raise Errors::InvalidData.new(res.body) if res.code == 400
85
+ raise Errors::UnknownError.new(res) if res.code != 200
86
+ @response = res.body
80
87
  end
81
88
 
82
89
  def parse_date
83
- DateTime.iso8601( Nokogiri::XML(@response.body).css("checkout date").first.content )
90
+ DateTime.iso8601( Nokogiri::XML(response.body).css("checkout date").first.content )
84
91
  end
85
92
 
86
93
  def parse_code
87
- Nokogiri::XML(@response.body).css("checkout code").first.content
94
+ Nokogiri::XML(response.body).css("checkout code").first.content
88
95
  end
89
96
  end
90
97
  end
@@ -0,0 +1,84 @@
1
+ module PagSeguro
2
+ class PreApproval
3
+ include ActiveModel::Validations
4
+ extend PagSeguro::ConvertFieldToDigit
5
+
6
+ PERIOD_TYPES = %w(weekly monthly bimonthly trimonthly semiannually yearly)
7
+ DAYS_OF_WEEK = %w(monday tuesday wednesday thursday friday saturday sunday)
8
+ DATE_RANGE = 17856.hours
9
+
10
+ attr_accessor :name, :details, :amount_per_payment, :period, :day_of_week, :day_of_month,
11
+ :day_of_year, :initial_date, :final_date, :max_amount_per_period, :max_total_amount, :review_URL
12
+ attr_reader_as_digit :amount_per_payment, :max_amount_per_period, :max_total_amount
13
+
14
+ validates_presence_of :name, :period, :final_date, :max_total_amount, :max_amount_per_period
15
+ validates_inclusion_of :period, in: PERIOD_TYPES
16
+ validates_inclusion_of :day_of_week, in: DAYS_OF_WEEK, if: :weekly?
17
+ validates_inclusion_of :day_of_month, in: (1..28), if: :monthly?
18
+ validates_presence_of :day_of_year, if: :yearly?
19
+ validates_format_of :day_of_year, with: /^\d{2}-\d{2}$/, if: :yearly?
20
+ validate :initial_date_range, :final_date_range
21
+ validates :max_amount_per_period, pagseguro_decimal: true
22
+ validates :max_total_amount, pagseguro_decimal: true
23
+
24
+ def initialize(options = {})
25
+ @name = options[:name]
26
+ @details = options[:details]
27
+ @amount_per_payment = options[:amount_per_payment]
28
+ @period = options[:period]
29
+ @day_of_week = options[:day_of_week]
30
+ @day_of_month = options[:day_of_month]
31
+ @day_of_year = options[:day_of_year]
32
+ @initial_date = options[:initial_date]
33
+ @final_date = options[:final_date]
34
+ @max_amount_per_period = options[:max_amount_per_period]
35
+ @max_total_amount = options[:max_total_amount]
36
+ @review_URL = options[:review_URL]
37
+ end
38
+
39
+ def period
40
+ @period.to_s.downcase
41
+ end
42
+
43
+ def day_of_week
44
+ @day_of_week.to_s.downcase
45
+ end
46
+
47
+ def day_of_year
48
+ @day_of_year.to_s
49
+ end
50
+
51
+ def initial_date
52
+ @initial_date.to_datetime if @initial_date.present?
53
+ end
54
+
55
+ def final_date
56
+ @final_date.to_datetime if @final_date.present?
57
+ end
58
+
59
+ def weekly?
60
+ period == 'weekly'
61
+ end
62
+
63
+ def monthly?
64
+ %w(monthly bimonthly trimonthly).include? period
65
+ end
66
+
67
+ def yearly?
68
+ period == 'yearly'
69
+ end
70
+
71
+ protected
72
+ def initial_date_range
73
+ return unless initial_date
74
+ errors.add(:initial_date) if initial_date < Time.now - 5.minutes
75
+ errors.add(:initial_date) if initial_date > DATE_RANGE.from_now
76
+ end
77
+
78
+ def final_date_range
79
+ return unless final_date
80
+ errors.add(:final_date) if final_date < (initial_date || Time.now) - 5.minutes
81
+ errors.add(:final_date) if final_date > (initial_date || Time.now) + DATE_RANGE
82
+ end
83
+ end
84
+ end
@@ -8,9 +8,35 @@ module PagSeguro
8
8
  @data = transaction_data(email, token, transaction_code)
9
9
  end
10
10
 
11
+ def self.find(email, token, options={})
12
+ url = Transaction::PAGSEGURO_TRANSACTIONS_URL
13
+ transactions_data = Nokogiri::XML(RestClient.get url, params: search_params(email, token, options))
14
+ transactions_data.css("transaction").map{|transaction_xml| Transaction.new(transaction_xml) }
15
+ end
16
+
17
+ def self.search_params(email, token, options={})
18
+ params = {email: email, token: token}
19
+ params[:initialDate], params[:finalDate] = parse_dates(options)
20
+ params[:page] = options[:page] if options[:page]
21
+ params[:maxPageResults] = options[:max_page_results] if options[:max_page_results]
22
+ params
23
+ end
24
+
25
+ def self.parse_dates(options={})
26
+ initial_date = (options[:initial_date] || Time.now - 1.day).to_time
27
+ final_date = (options[:final_date] || initial_date + 1.day).to_time
28
+
29
+ raise "Invalid initial date. Must be bigger than 6 months ago" if initial_date < 6.months.ago
30
+ raise "Invalid end date. Must be less than today" if final_date > Date.today.end_of_day
31
+ raise "Invalid end date. Must be bigger than initial date" if final_date < initial_date
32
+ raise "Invalid end date. Must not differ from initial date in more than 30 days" if (final_date.to_date - initial_date.to_date) > 30
33
+
34
+ return initial_date.to_time.iso8601, final_date.to_time.iso8601
35
+ end
36
+
11
37
  private
12
38
  def transaction_data(email, token, transaction_code)
13
- super(RestClient.get("#{PAGSEGURO_TRANSACTIONS_URL}/#{transaction_code}?email=#{email}&token=#{token}"))
39
+ super RestClient.get "#{PAGSEGURO_TRANSACTIONS_URL}/#{transaction_code}", params: {email: email, token: token}
14
40
  end
15
41
  end
16
42
  end
@@ -27,11 +27,11 @@ module PagSeguro
27
27
  end
28
28
 
29
29
  def phone_ddd
30
- @phone_ddd if @phone_ddd =~ /^\d{2}$/
30
+ @phone_ddd if @phone_ddd.to_s =~ /^\d{2}$/
31
31
  end
32
32
 
33
33
  def phone_number
34
- @phone_number if @phone_number =~/^\d{8,9}$/
34
+ @phone_number if @phone_number.to_s =~/^\d{8,9}$/
35
35
  end
36
36
  end
37
37
  end
@@ -6,12 +6,12 @@ module PagSeguro
6
6
  SEDEX = 2
7
7
  UNIDENTIFIED = 3
8
8
 
9
- validates_format_of :postal_code, with: /^\d{8}$/, message: " must be an integer with 8 digits", allow_blank: true
9
+ validates :postal_code, numericality: true, length: {is: 8}
10
10
 
11
11
  attr_accessor :type, :state, :city, :postal_code, :district, :street, :number, :complement, :cost
12
12
 
13
13
  def initialize(attributes = {})
14
- @type = attributes[:type]
14
+ @type = attributes[:type] || UNIDENTIFIED
15
15
  @state = attributes[:state]
16
16
  @city = attributes[:city]
17
17
  @postal_code = attributes[:postal_code]
@@ -170,7 +170,7 @@ module PagSeguro
170
170
 
171
171
  protected
172
172
  def transaction_data(transaction_xml)
173
- Nokogiri::XML(transaction_xml)
173
+ transaction_xml.instance_of?(Nokogiri::XML::Element) ? transaction_xml : Nokogiri::XML(transaction_xml)
174
174
  end
175
175
 
176
176
  def parse_item(data, attribute)
@@ -1,3 +1,3 @@
1
1
  module PagSeguro
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.1"
3
3
  end
data/lib/pag_seguro.rb CHANGED
@@ -1,12 +1,18 @@
1
1
  $: << File.expand_path(File.dirname(__FILE__) + "/../lib/pag_seguro")
2
2
 
3
- require 'date'
3
+ require "date"
4
+ require "bigdecimal"
4
5
 
5
6
  # Third party gems
6
7
  require "active_model"
7
8
  require "nokogiri"
8
9
  require "haml"
9
10
  require "rest-client"
11
+ require "active_support"
12
+ require "active_support/time"
13
+
14
+ require "pagseguro_decimal_validator"
15
+ require "convert_field_to_digit"
10
16
 
11
17
  # PagSeguro classes
12
18
  require "item"
@@ -14,6 +20,8 @@ require "payment"
14
20
  require "payment_method"
15
21
  require "sender"
16
22
  require "shipping"
23
+ require "day_of_year"
24
+ require "pre_approval"
17
25
  require "transaction"
18
26
  require "notification"
19
27
  require "query"
@@ -0,0 +1,9 @@
1
+ class PagseguroDecimalValidator < ActiveModel::EachValidator
2
+ def validate_each(object, attribute, value)
3
+ object.errors.add(attribute, error_message) unless value.nil? || value =~ /^\d+\.\d{2}$/
4
+ end
5
+
6
+ def error_message
7
+ " must be a decimal and have 2 digits after the dot"
8
+ end
9
+ end
data/pag_seguro.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.has_rdoc = false
20
20
 
21
21
  s.add_dependency('activemodel')
22
+ s.add_dependency('activesupport')
22
23
  s.add_dependency('haml', '!= 3.1.5')
23
24
  s.add_dependency('nokogiri')
24
25
  s.add_dependency('rest-client', '~> 1.6.7')