adyen 1.5.0 → 1.6.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.
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