adyen 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbc7e880acbef87255749e6de239137a815ce5db
4
- data.tar.gz: 5cca9c9e9af626b4f0adfd4626d83212735cb0a2
3
+ metadata.gz: 455a933ae555a5bdef76141cf1b8ff4d7e44b383
4
+ data.tar.gz: a6f844a752ce6f69778bd9eba5bcfc86a935404c
5
5
  SHA512:
6
- metadata.gz: 3e942a606d19d158af7a424a87489c614a42efc909c437cee1cd18c05bd470c9311b38a77dc50b4a85e872fe87b84be6e47bc5b3435e7e7ad9253366051c6f90
7
- data.tar.gz: 0b52b3698293f517fae0588a12b9e65016d840670085fb653a3ae715b35e3f18776500760101e5bd23aba72d69d3f2e351a5b70d143739c5524286b21d1639e6
6
+ metadata.gz: 997202692973d0ab546fca1ee3c343a1e0c7e7809b2f4a8d6cf7636bc34ced06b3d31bcfeae77339472483888d5b0db10a56f70c47a6ddffed49eb6f7c6b0ed5
7
+ data.tar.gz: b591907d17db7636db322d851c796c6ae6b2b247b3c8cac2fba8d4bee63aa940596469ff9946ce2f818467a4253c53572947381d2eb80994dcb9c61ba11ce399
data/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
+ .idea
2
+ .gems
3
+ .rbenv-gemsets
1
4
  /tmp
2
5
  /pkg
3
6
  /doc
@@ -18,5 +18,5 @@ before_script:
18
18
  env:
19
19
  global:
20
20
  - ADYEN_MERCHANT_ACCOUNT: "VanBergenORG"
21
- - ADYEN_API_USERNAME: "ws_052255@Company.VanBergen"
22
- - ADYEN_API_PASSWORD: "xoYyauhxwsgAqs3vNr3h"
21
+ - ADYEN_API_USERNAME: "ws@Company.VanBergen"
22
+ - ADYEN_API_PASSWORD: "7phtHzbfnzsp"
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 - 2011
1
+ Copyright (c) 2008 - 2014
2
2
  Willem van Bergen, Michel Barbosa, Stefan Borsje & Eloy Duran
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining
@@ -41,6 +41,10 @@ Contributions are welcomed; this is very much a scratch your own itch project. S
41
41
  * All functionality must include tests and preferably documentation.
42
42
  * New SOAP API calls should include functional tests that actually test if the call is working.
43
43
  Adyen has a nasty tendency to switch things up every now and then, so this is vital.
44
+ Note: we use a test account to run the functional specs on CI. Some API calls have to be
45
+ enabled on Adyen's side before our test account can use them. If you receive failures with
46
+ "010 Not allowed" on CI while it works fine with your owen test account, please let us know on
47
+ the pull request, so I can ask Adyen to enable the feature on the test account as well.
44
48
 
45
49
  Please visit the changelog at https://github.com/wvanbergen/adyen/wiki/Changelog to see the
46
50
  changes in the different releases.
data/Rakefile CHANGED
@@ -1,9 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require "rake/testtask"
3
4
 
4
- Dir['tasks/*.rake'].each { |file| load(file) }
5
-
6
-
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
7
9
 
8
10
  RSpec::Core::RakeTask.new(:spec) do |task|
9
11
  task.pattern = "./spec/**/*_spec.rb"
@@ -22,7 +24,7 @@ end
22
24
 
23
25
  # Update the cacert.pem file before each release.
24
26
  task :build => :update_cacert do
25
- sh "git commit #{CACERT_PATH} -m '[API] Update CA root certificates file.'"
27
+ sh "git diff-index --quiet HEAD #{CACERT_PATH} || (git add #{CACERT_PATH} && git commit -m '[API] Update CA root certificates file.')"
26
28
  end
27
29
 
28
30
  begin
@@ -35,4 +37,4 @@ begin
35
37
  rescue LoadError
36
38
  end
37
39
 
38
- task :default => [:spec]
40
+ task :default => [:test, :spec]
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
25
25
 
26
26
  s.add_development_dependency('rake')
27
27
  s.add_development_dependency('rspec', '~> 2.14')
28
+ s.add_development_dependency('minitest', '~> 5')
29
+ s.add_development_dependency('mocha')
28
30
 
29
31
  s.add_development_dependency('rails', '>= 3.2')
30
32
  s.add_development_dependency('nokogiri', '>= 1.6.1')
@@ -50,6 +50,63 @@ module Adyen
50
50
  module API
51
51
  extend self
52
52
 
53
+ # Generate a Billet - *Brazian users only*
54
+ #
55
+ # Billet (Boleto Bancário), often simply referred to as Boleto, is an
56
+ # offline payment method used in Brazil . The consumer will take the Boleto form to
57
+ # an ATM, bank, an approved facility, or access their online banking system
58
+ # to complete the payment. Once the Boleto is paid, the bank will send Adyen
59
+ # a file confirming that the payment was made, this usually takes one day, but
60
+ # it may occur up to 6 days after the payment. If a Boleto is not paid, the
61
+ # transaction will expire once the expirationDate is reached. For more
62
+ # information check the Adyen API Manual - 7 - Boleto Bancário(page 30)
63
+ #
64
+ # @example
65
+ # response = Adyen::API.generate_billet(
66
+ # invoice.id
67
+ # { currency: "BRL", value: (invoice.amount).to_i },
68
+ # { first_name: "Simon", last_name: "Hopper" },
69
+ # document_number,
70
+ # selected_brand
71
+ # )
72
+ # response.success? # => true
73
+ #
74
+ #
75
+ # @param [Numeric,String] reference Your reference (ID) for this payment.
76
+ # @param [Hash] amount A hash describing the money to charge.
77
+ # @param [Hash] shopper A hash describing the shopper.
78
+ # @param [String] document_number Social Security
79
+ # number(CPF in Brazil)
80
+ # @param [String] selected_brand Billet brand
81
+ #
82
+ # @option amount [String] :currency The ISO currency code (EUR, GBP, USD, etc).
83
+ # @option amount [Integer] :value The value of the payment in discrete cents,
84
+ # unless the currency does not have cents.
85
+ #
86
+ # @option shopper_name [String] :first_name The shopper’s first name
87
+ # @option shopper_name [String] :last_name The shopper’s last name
88
+ #
89
+ #
90
+ # @return [PaymentService::BilletResponse] The response object which holds the billet url.
91
+ #
92
+ def generate_billet(reference, amount, shopper_name, social_security_number, selected_brand, delivery_date)
93
+ params = { :reference => reference,
94
+ :amount => amount,
95
+ :shopper_name => shopper_name,
96
+ :social_security_number => social_security_number,
97
+ :selected_brand => selected_brand,
98
+ :delivery_date => delivery_date }
99
+ PaymentService.new(params).generate_billet
100
+ end
101
+
102
+ # Make an instant payment.
103
+ #
104
+ # Technically - authorisation with immediate (no delay) capture.
105
+ # @see authorise_payment
106
+ def pay_instantly(reference, amount, shopper, card, enable_recurring_contract = false, fraud_offset = nil)
107
+ authorise_payment(reference, amount, shopper, card, enable_recurring_contract, fraud_offset, true)
108
+ end
109
+
53
110
  # Authorise a payment.
54
111
  #
55
112
  # @see capture_payment
@@ -96,13 +153,14 @@ module Adyen
96
153
  #
97
154
  # @return [PaymentService::AuthorisationResponse] The response object which holds the
98
155
  # authorisation status.
99
- def authorise_payment(reference, amount, shopper, card, enable_recurring_contract = false, fraud_offset = nil)
156
+ def authorise_payment(reference, amount, shopper, card, enable_recurring_contract = false, fraud_offset = nil, instant_capture = false)
100
157
  params = { :reference => reference,
101
158
  :amount => amount,
102
159
  :shopper => shopper,
103
160
  :card => card,
104
161
  :recurring => enable_recurring_contract,
105
- :fraud_offset => fraud_offset }
162
+ :fraud_offset => fraud_offset,
163
+ :instant_capture => instant_capture }
106
164
  PaymentService.new(params).authorise_payment
107
165
  end
108
166
 
@@ -143,12 +201,13 @@ module Adyen
143
201
  #
144
202
  # @return [PaymentService::AuthorisationResponse] The response object which holds the
145
203
  # authorisation status.
146
- def authorise_recurring_payment(reference, amount, shopper, recurring_detail_reference = 'LATEST', fraud_offset = nil)
204
+ def authorise_recurring_payment(reference, amount, shopper, recurring_detail_reference = 'LATEST', fraud_offset = nil, instant_capture = false)
147
205
  params = { :reference => reference,
148
206
  :amount => amount,
149
207
  :shopper => shopper,
150
208
  :recurring_detail_reference => recurring_detail_reference,
151
- :fraud_offset => fraud_offset }
209
+ :fraud_offset => fraud_offset,
210
+ :instant_capture => instant_capture }
152
211
  PaymentService.new(params).authorise_recurring_payment
153
212
  end
154
213
 
@@ -194,13 +253,14 @@ module Adyen
194
253
  #
195
254
  # @return [PaymentService::AuthorisationResponse] The response object which holds the
196
255
  # authorisation status.
197
- def authorise_one_click_payment(reference, amount, shopper, card, recurring_detail_reference, fraud_offset = nil)
256
+ def authorise_one_click_payment(reference, amount, shopper, card, recurring_detail_reference, fraud_offset = nil, instant_capture = false)
198
257
  params = { :reference => reference,
199
258
  :amount => amount,
200
259
  :shopper => shopper,
201
260
  :card => card,
202
261
  :recurring_detail_reference => recurring_detail_reference,
203
- :fraud_offset => fraud_offset }
262
+ :fraud_offset => fraud_offset,
263
+ :instant_capture => instant_capture }
204
264
  PaymentService.new(params).authorise_one_click_payment
205
265
  end
206
266
 
@@ -40,6 +40,11 @@ module Adyen
40
40
  # The Adyen Payment SOAP service endpoint uri.
41
41
  ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Payment'
42
42
 
43
+ # @see API.generate_billet
44
+ def generate_billet
45
+ make_payment_request(generate_billet_request_body, BilletResponse)
46
+ end
47
+
43
48
  # @see API.authorise_payment
44
49
  def authorise_payment
45
50
  make_payment_request(authorise_payment_request_body, AuthorisationResponse)
@@ -110,6 +115,17 @@ module Adyen
110
115
  content << installments_partial if @params[:installments]
111
116
  content << shopper_partial if @params[:shopper]
112
117
  content << fraud_offset_partial if @params[:fraud_offset]
118
+ content << capture_delay_partial if @params[:instant_capture]
119
+ LAYOUT % [@params[:merchant_account], @params[:reference], content]
120
+ end
121
+
122
+ def generate_billet_request_body
123
+ validate_parameters!(:merchant_account, :reference, :amount => [:currency, :value])
124
+ content = amount_partial
125
+ content << social_security_number_partial if @params[:social_security_number]
126
+ content << shopper_name_partial if @params[:shopper_name]
127
+ content << delivery_date_partial if @params[:delivery_date]
128
+ content << selected_brand_partial if @params[:selected_brand]
113
129
  LAYOUT % [@params[:merchant_account], @params[:reference], content]
114
130
  end
115
131
 
@@ -150,13 +166,18 @@ module Adyen
150
166
  end
151
167
  end
152
168
 
169
+ def shopper_name_partial
170
+ SHOPPER_NAME_PARTIAL % @params[:shopper_name].values_at(:first_name, :last_name)
171
+ end
172
+
153
173
  def card_partial
154
174
  if @params[:card] && @params[:card][:encrypted] && @params[:card][:encrypted][:json]
155
175
  ENCRYPTED_CARD_PARTIAL % [@params[:card][:encrypted][:json]]
156
176
  else
157
- validate_parameters!(:card => [:holder_name, :number, :cvc, :expiry_year, :expiry_month])
158
- card = @params[:card].values_at(:holder_name, :number, :cvc, :expiry_year)
177
+ validate_parameters!(:card => [:holder_name, :number, :expiry_year, :expiry_month])
178
+ card = @params[:card].values_at(:holder_name, :number, :expiry_year)
159
179
  card << @params[:card][:expiry_month].to_i
180
+ card << (['', nil].include?(@params[:card][:cvc]) ? '' : (CARD_CVC_PARTIAL % @params[:card][:cvc]))
160
181
  CARD_PARTIAL % card
161
182
  end
162
183
  end
@@ -167,6 +188,24 @@ module Adyen
167
188
  end
168
189
  end
169
190
 
191
+ def social_security_number_partial
192
+ if @params[:social_security_number]
193
+ SOCIAL_SECURITY_NUMBER_PARTIAL % @params[:social_security_number]
194
+ end
195
+ end
196
+
197
+ def selected_brand_partial
198
+ if @params[:selected_brand]
199
+ SELECTED_BRAND_PARTIAL % @params[:selected_brand]
200
+ end
201
+ end
202
+
203
+ def delivery_date_partial
204
+ if @params[:delivery_date]
205
+ DELIVERY_DATE_PARTIAL % @params[:delivery_date]
206
+ end
207
+ end
208
+
170
209
  def shopper_partial
171
210
  @params[:shopper].map { |k, v| SHOPPER_PARTIALS[k] % v }.join("\n")
172
211
  end
@@ -176,6 +215,34 @@ module Adyen
176
215
  FRAUD_OFFSET_PARTIAL % @params[:fraud_offset]
177
216
  end
178
217
 
218
+ def capture_delay_partial(delay = 0)
219
+ CAPTURE_DELAY_PARTIAL % delay
220
+ end
221
+
222
+ class BilletResponse < Response
223
+ RECEIVED = "Received"
224
+
225
+ response_attrs :result_code, :billet_url, :psp_reference
226
+
227
+ def success?
228
+ super && params[:result_code] == RECEIVED
229
+ end
230
+
231
+ def params
232
+ @params ||= xml_querier.xpath('//payment:authoriseResponse/payment:paymentResult') do |result|
233
+ {
234
+ :psp_reference => result.text('./payment:pspReference'),
235
+ :result_code => result_code = result.text('./payment:resultCode'),
236
+ :billet_url => (result_code == RECEIVED) ? result.children[0].children[0].children[1].text : ""
237
+ }
238
+ end
239
+ end
240
+
241
+ def invalid_request?
242
+ !fault_message.nil?
243
+ end
244
+ end
245
+
179
246
  class AuthorisationResponse < Response
180
247
  ERRORS = {
181
248
  "validation 101 Invalid card number" => [:number, 'is not a valid creditcard number'],
@@ -73,7 +73,7 @@ module Adyen
73
73
  content << card_partial unless @params[:card].nil?
74
74
  content << elv_partial unless @params[:elv].nil?
75
75
  raise ArgumentError, "The required parameter 'card' or 'elv' is missing." if content.empty?
76
- STORE_TOKEN_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference], @params[:shopper][:email], content]
76
+ STORE_TOKEN_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference], @params[:shopper][:email], content.join]
77
77
  end
78
78
 
79
79
  class DisableResponse < Response
@@ -59,12 +59,17 @@ module Adyen
59
59
  <payment:card>
60
60
  <payment:holderName>%s</payment:holderName>
61
61
  <payment:number>%s</payment:number>
62
- <payment:cvc>%s</payment:cvc>
63
62
  <payment:expiryYear>%s</payment:expiryYear>
64
63
  <payment:expiryMonth>%02d</payment:expiryMonth>
64
+ %s
65
65
  </payment:card>
66
66
  EOXML
67
67
 
68
+ # @private
69
+ CARD_CVC_PARTIAL = <<-EOXML
70
+ <payment:cvc>%s</payment:cvc>
71
+ EOXML
72
+
68
73
  # @private
69
74
  ONE_CLICK_CARD_PARTIAL = <<-EOXML
70
75
  <payment:card>
@@ -79,6 +84,28 @@ module Adyen
79
84
  </payment:installments>
80
85
  EOXML
81
86
 
87
+ SOCIAL_SECURITY_NUMBER_PARTIAL = <<-EOXML
88
+ <payment:socialSecurityNumber>%s</payment:socialSecurityNumber>
89
+ EOXML
90
+
91
+ # @private
92
+ DELIVERY_DATE_PARTIAL = <<-EOXML
93
+ <deliveryDate xmlns="http://payment.services.adyen.com">%s</deliveryDate>
94
+ EOXML
95
+
96
+ # @private
97
+ SELECTED_BRAND_PARTIAL = <<-EOXML
98
+ <payment:selectedBrand>%s</payment:selectedBrand>
99
+ EOXML
100
+
101
+ # @private
102
+ SHOPPER_NAME_PARTIAL = <<-EOXML
103
+ <payment:shopperName>
104
+ <common:firstName>%s</common:firstName>
105
+ <common:lastName>%s</common:lastName>
106
+ </payment:shopperName>
107
+ EOXML
108
+
82
109
  # @private
83
110
  ENCRYPTED_CARD_PARTIAL = <<-EOXML
84
111
  <additionalAmount xmlns="http://payment.services.adyen.com" xsi:nil="true" />
@@ -124,6 +151,9 @@ module Adyen
124
151
 
125
152
  # @private
126
153
  FRAUD_OFFSET_PARTIAL = '<payment:fraudOffset>%s</payment:fraudOffset>'
154
+
155
+ # @private
156
+ CAPTURE_DELAY_PARTIAL = '<payment:captureDelayHours>%s</payment:captureDelayHours>'
127
157
  end
128
158
  end
129
159
  end
@@ -51,7 +51,7 @@ module Adyen
51
51
  end
52
52
 
53
53
  def perform_xpath(query, root_node)
54
- REXML::XPath.match(root_node, query, NS)
54
+ REXML::XPath.match(root_node, query, NS)
55
55
  end
56
56
 
57
57
  def stringify_nodeset(nodeset)
@@ -103,6 +103,7 @@ module Adyen
103
103
  # @return [Hash] The payment parameters with the +:merchant_signature+ parameter set.
104
104
  # @raise [ArgumentError] Thrown if some parameter health check fails.
105
105
  def payment_parameters(parameters = {}, shared_secret = nil)
106
+ raise ArgumentError, "Cannot generate form: parameters should be a hash!" unless parameters.is_a?(Hash)
106
107
  do_parameter_transformations!(parameters)
107
108
 
108
109
  raise ArgumentError, "Cannot generate form: :currency code attribute not found!" unless parameters[:currency_code]
@@ -356,6 +357,8 @@ module Adyen
356
357
  # using the {Adyen::Configuration#register_form_skin} method.
357
358
  # @return [true, false] Returns true only if the signature in the parameters is correct.
358
359
  def redirect_signature_check(params, shared_secret = nil)
360
+ raise ArgumentError, "params should be a Hash" unless params.is_a?(Hash)
361
+ raise ArgumentError, "params should contain :merchantSig" unless params.key?(:merchantSig)
359
362
  params[:merchantSig] == redirect_signature(params, shared_secret)
360
363
  end
361
364
 
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  module Adyen
2
4
  module Formatter
3
5
  module DateTime
@@ -6,9 +8,11 @@ module Adyen
6
8
  case date
7
9
  when Date, DateTime, Time
8
10
  date.strftime('%Y-%m-%d')
9
- else
10
- raise "Invalid date notation: #{date.inspect}!" unless /^\d{4}-\d{2}-\d{2}$/ =~ date
11
+ when String
12
+ raise ArgumentError, "Invalid date notation: #{date.inspect}!" unless /^\d{4}-\d{2}-\d{2}$/ =~ date
11
13
  date
14
+ else
15
+ raise ArgumentError, "Cannot convert #{date.inspect} to date!"
12
16
  end
13
17
  end
14
18
 
@@ -17,9 +21,11 @@ module Adyen
17
21
  case time
18
22
  when Date, DateTime, Time
19
23
  time.strftime('%Y-%m-%dT%H:%M:%SZ')
20
- else
21
- raise "Invalid timestamp notation: #{time.inspect}!" unless /^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z$/ =~ time
24
+ when String
25
+ raise ArgumentError, "Invalid timestamp notation: #{time.inspect}!" unless /^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z$/ =~ time
22
26
  time
27
+ else
28
+ raise ArgumentError, "Cannot convert #{time.inspect} to timestamp!"
23
29
  end
24
30
  end
25
31
  end
@@ -21,7 +21,7 @@ module Adyen
21
21
 
22
22
  # Add a check for all the other fields specified
23
23
  checks.each do |key, value|
24
- condition = "descendant::input[@type='hidden'][@name='#{Adyen::Form.camelize(key)}']"
24
+ condition = "\n descendant::input[@type='hidden'][@name='#{Adyen::Form.camelize(key)}']"
25
25
  condition << "[@value='#{value}']" unless value == :anything
26
26
  xpath_query << "[#{condition}]"
27
27
  end
@@ -30,12 +30,9 @@ module Adyen
30
30
  end
31
31
 
32
32
  def self.check(subject, checks = {})
33
- found = false
34
33
  document = Adyen::API::XMLQuerier.html(subject)
35
- document.xpath(build_xpath_query(checks)) do |result|
36
- found = true
37
- end
38
- return found
34
+ result = document.xpath(build_xpath_query(checks))
35
+ !result.empty?
39
36
  end
40
37
  end
41
38
 
@@ -91,6 +88,5 @@ module Adyen
91
88
  recurring_checks = { :recurring => false }
92
89
  assert_adyen_payment_form(subject, recurring_checks.merge(checks))
93
90
  end
94
-
95
91
  end
96
92
  end