pag_seguro 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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')