adyen 0.3.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.kick +35 -0
- data/LICENSE +3 -2
- data/README.rdoc +8 -4
- data/Rakefile +10 -0
- data/TODO +14 -4
- data/adyen.gemspec +9 -15
- data/lib/adyen.rb +10 -59
- data/lib/adyen/api.rb +281 -0
- data/lib/adyen/api/cacert.pem +3509 -0
- data/lib/adyen/api/payment_service.rb +258 -0
- data/lib/adyen/api/recurring_service.rb +126 -0
- data/lib/adyen/api/response.rb +54 -0
- data/lib/adyen/api/simple_soap_client.rb +118 -0
- data/lib/adyen/api/templates/payment_service.rb +103 -0
- data/lib/adyen/api/templates/recurring_service.rb +34 -0
- data/lib/adyen/api/test_helpers.rb +133 -0
- data/lib/adyen/api/xml_querier.rb +94 -0
- data/lib/adyen/configuration.rb +139 -0
- data/lib/adyen/form.rb +37 -109
- data/lib/adyen/formatter.rb +0 -10
- data/lib/adyen/matchers.rb +1 -1
- data/lib/adyen/notification_generator.rb +30 -0
- data/lib/adyen/railtie.rb +13 -0
- data/lib/adyen/templates/notification_migration.rb +29 -0
- data/lib/adyen/templates/notification_model.rb +70 -0
- data/spec/adyen_spec.rb +3 -45
- data/spec/api/api_spec.rb +139 -0
- data/spec/api/payment_service_spec.rb +439 -0
- data/spec/api/recurring_service_spec.rb +105 -0
- data/spec/api/response_spec.rb +35 -0
- data/spec/api/simple_soap_client_spec.rb +91 -0
- data/spec/api/spec_helper.rb +417 -0
- data/spec/api/test_helpers_spec.rb +83 -0
- data/spec/form_spec.rb +27 -23
- data/spec/functional/api_spec.rb +90 -0
- data/spec/functional/initializer.rb.sample +3 -0
- data/spec/spec_helper.rb +5 -5
- data/tasks/github-gem.rake +49 -55
- data/yard_extensions.rb +16 -0
- metadata +63 -82
- data/init.rb +0 -1
- data/lib/adyen/notification.rb +0 -151
- data/lib/adyen/soap.rb +0 -649
- data/spec/notification_spec.rb +0 -97
- data/spec/soap_spec.rb +0 -340
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'adyen/api/simple_soap_client'
|
2
|
+
require 'adyen/api/templates/payment_service'
|
3
|
+
|
4
|
+
module Adyen
|
5
|
+
module API
|
6
|
+
# This is the class that maps actions to Adyen’s Payment SOAP service.
|
7
|
+
#
|
8
|
+
# It’s encouraged to use the shortcut methods on the {API} module, which abstracts away the
|
9
|
+
# difference between this service and the {RecurringService}. Henceforth, for extensive
|
10
|
+
# documentation you should look at the {API} documentation.
|
11
|
+
#
|
12
|
+
# The most important difference is that you instantiate a {PaymentService} with the parameters
|
13
|
+
# that are needed for the call that you will eventually make.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# payment = Adyen::API::PaymentService.new({
|
17
|
+
# :reference => invoice.id,
|
18
|
+
# :amount => {
|
19
|
+
# :currency => 'EUR',
|
20
|
+
# :value => invoice.amount,
|
21
|
+
# },
|
22
|
+
# :shopper => {
|
23
|
+
# :email => user.email,
|
24
|
+
# :reference => user.id,
|
25
|
+
# #:ip => request.,
|
26
|
+
# },
|
27
|
+
# :card => {
|
28
|
+
# :expiry_month => 12,
|
29
|
+
# :expiry_year => 2012,
|
30
|
+
# :holder_name => 'Simon Hopper',
|
31
|
+
# :number => '4444333322221111',
|
32
|
+
# :cvc => '737'
|
33
|
+
# }
|
34
|
+
# })
|
35
|
+
# response = payment.authorise_payment
|
36
|
+
# response.authorised? # => true
|
37
|
+
#
|
38
|
+
class PaymentService < SimpleSOAPClient
|
39
|
+
# The Adyen Payment SOAP service endpoint uri.
|
40
|
+
ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Payment'
|
41
|
+
|
42
|
+
# @see API.authorise_payment
|
43
|
+
def authorise_payment
|
44
|
+
make_payment_request(authorise_payment_request_body, AuthorisationResponse)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @see API.authorise_recurring_payment
|
48
|
+
def authorise_recurring_payment
|
49
|
+
make_payment_request(authorise_recurring_payment_request_body, AuthorisationResponse)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @see API.authorise_one_click_payment
|
53
|
+
def authorise_one_click_payment
|
54
|
+
make_payment_request(authorise_one_click_payment_request_body, AuthorisationResponse)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @see API.capture_payment
|
58
|
+
def capture
|
59
|
+
make_payment_request(capture_request_body, CaptureResponse)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @see API.refund_payment
|
63
|
+
def refund
|
64
|
+
make_payment_request(refund_request_body, RefundResponse)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @see API.cancel_payment
|
68
|
+
def cancel
|
69
|
+
make_payment_request(cancel_request_body, CancelResponse)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @see API.cancel_or_refund_payment
|
73
|
+
def cancel_or_refund
|
74
|
+
make_payment_request(cancel_or_refund_request_body, CancelOrRefundResponse)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def make_payment_request(data, response_class)
|
80
|
+
call_webservice_action('authorise', data, response_class)
|
81
|
+
end
|
82
|
+
|
83
|
+
def authorise_payment_request_body
|
84
|
+
content = card_partial
|
85
|
+
if @params[:recurring]
|
86
|
+
validate_parameters!(:shopper => [:email, :reference])
|
87
|
+
content << ENABLE_RECURRING_CONTRACTS_PARTIAL
|
88
|
+
end
|
89
|
+
payment_request_body(content)
|
90
|
+
end
|
91
|
+
|
92
|
+
def authorise_recurring_payment_request_body
|
93
|
+
validate_parameters!(:shopper => [:email, :reference])
|
94
|
+
content = RECURRING_PAYMENT_BODY_PARTIAL % (@params[:recurring_detail_reference] || 'LATEST')
|
95
|
+
payment_request_body(content)
|
96
|
+
end
|
97
|
+
|
98
|
+
def authorise_one_click_payment_request_body
|
99
|
+
validate_parameters!(:recurring_detail_reference,
|
100
|
+
:shopper => [:email, :reference],
|
101
|
+
:card => [:cvc])
|
102
|
+
content = ONE_CLICK_PAYMENT_BODY_PARTIAL % [@params[:recurring_detail_reference], @params[:card][:cvc]]
|
103
|
+
payment_request_body(content)
|
104
|
+
end
|
105
|
+
|
106
|
+
def payment_request_body(content)
|
107
|
+
validate_parameters!(:merchant_account, :reference, :amount => [:currency, :value])
|
108
|
+
content << amount_partial
|
109
|
+
content << shopper_partial if @params[:shopper]
|
110
|
+
LAYOUT % [@params[:merchant_account], @params[:reference], content]
|
111
|
+
end
|
112
|
+
|
113
|
+
def capture_request_body
|
114
|
+
CAPTURE_LAYOUT % capture_and_refund_params
|
115
|
+
end
|
116
|
+
|
117
|
+
def refund_request_body
|
118
|
+
REFUND_LAYOUT % capture_and_refund_params
|
119
|
+
end
|
120
|
+
|
121
|
+
def cancel_or_refund_request_body
|
122
|
+
validate_parameters!(:merchant_account, :psp_reference)
|
123
|
+
CANCEL_OR_REFUND_LAYOUT % [@params[:merchant_account], @params[:psp_reference]]
|
124
|
+
end
|
125
|
+
|
126
|
+
def cancel_request_body
|
127
|
+
validate_parameters!(:merchant_account, :psp_reference)
|
128
|
+
CANCEL_LAYOUT % [@params[:merchant_account], @params[:psp_reference]]
|
129
|
+
end
|
130
|
+
|
131
|
+
def capture_and_refund_params
|
132
|
+
validate_parameters!(:merchant_account, :psp_reference, :amount => [:currency, :value])
|
133
|
+
[@params[:merchant_account], @params[:psp_reference], *@params[:amount].values_at(:currency, :value)]
|
134
|
+
end
|
135
|
+
|
136
|
+
def amount_partial
|
137
|
+
AMOUNT_PARTIAL % @params[:amount].values_at(:currency, :value)
|
138
|
+
end
|
139
|
+
|
140
|
+
def card_partial
|
141
|
+
validate_parameters!(:card => [:holder_name, :number, :cvc, :expiry_year, :expiry_month])
|
142
|
+
card = @params[:card].values_at(:holder_name, :number, :cvc, :expiry_year)
|
143
|
+
card << @params[:card][:expiry_month].to_i
|
144
|
+
CARD_PARTIAL % card
|
145
|
+
end
|
146
|
+
|
147
|
+
def shopper_partial
|
148
|
+
@params[:shopper].map { |k, v| SHOPPER_PARTIALS[k] % v }.join("\n")
|
149
|
+
end
|
150
|
+
|
151
|
+
class AuthorisationResponse < Response
|
152
|
+
ERRORS = {
|
153
|
+
"validation 101 Invalid card number" => [:number, 'is not a valid creditcard number'],
|
154
|
+
"validation 103 CVC is not the right length" => [:cvc, 'is not the right length'],
|
155
|
+
"validation 128 Card Holder Missing" => [:holder_name, "can't be blank"],
|
156
|
+
"validation Couldn't parse expiry year" => [:expiry_year, 'could not be recognized'],
|
157
|
+
"validation Expiry month should be between 1 and 12 inclusive" => [:expiry_month, 'could not be recognized'],
|
158
|
+
}
|
159
|
+
|
160
|
+
AUTHORISED = 'Authorised'
|
161
|
+
|
162
|
+
def self.original_fault_message_for(attribute, message)
|
163
|
+
if error = ERRORS.find { |_, (a, m)| a == attribute && m == message }
|
164
|
+
error.first
|
165
|
+
else
|
166
|
+
message
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
response_attrs :result_code, :auth_code, :refusal_reason, :psp_reference
|
171
|
+
|
172
|
+
def success?
|
173
|
+
super && params[:result_code] == AUTHORISED
|
174
|
+
end
|
175
|
+
|
176
|
+
alias authorized? success?
|
177
|
+
|
178
|
+
# @return [Boolean] Returns whether or not the request was valid.
|
179
|
+
def invalid_request?
|
180
|
+
!fault_message.nil?
|
181
|
+
end
|
182
|
+
|
183
|
+
# In the case of a validation error, or SOAP fault message, this method will return an
|
184
|
+
# array describing what attribute failed validation and the accompanying message. If the
|
185
|
+
# errors is not of the common user validation errors, then the attribute is +:base+ and the
|
186
|
+
# full original message is returned.
|
187
|
+
#
|
188
|
+
# An optional +prefix+ can be given so you can seamlessly integrate this in your
|
189
|
+
# ActiveRecord model and copy over errors.
|
190
|
+
#
|
191
|
+
# @param [String,Symbol] prefix A string that should be used to prefix the error key.
|
192
|
+
# @return [Array<Symbol, String>] A name-message pair of the attribute with an error.
|
193
|
+
def error(prefix = nil)
|
194
|
+
if error = ERRORS[fault_message]
|
195
|
+
prefix ? ["#{prefix}_#{error[0]}".to_sym, error[1]] : error
|
196
|
+
else
|
197
|
+
[:base, fault_message]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def params
|
202
|
+
@params ||= xml_querier.xpath('//payment:authoriseResponse/payment:paymentResult') do |result|
|
203
|
+
{
|
204
|
+
:psp_reference => result.text('./payment:pspReference'),
|
205
|
+
:result_code => result.text('./payment:resultCode'),
|
206
|
+
:auth_code => result.text('./payment:authCode'),
|
207
|
+
:refusal_reason => (invalid_request? ? fault_message : result.text('./payment:refusalReason'))
|
208
|
+
}
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class ModificationResponse < Response
|
214
|
+
class << self
|
215
|
+
# @private
|
216
|
+
attr_accessor :request_received_value, :base_xpath
|
217
|
+
end
|
218
|
+
|
219
|
+
response_attrs :psp_reference, :response
|
220
|
+
|
221
|
+
# This only returns whether or not the request has been successfully received. Check the
|
222
|
+
# subsequent notification to see if the payment was actually mutated.
|
223
|
+
def success?
|
224
|
+
super && params[:response] == self.class.request_received_value
|
225
|
+
end
|
226
|
+
|
227
|
+
def params
|
228
|
+
@params ||= xml_querier.xpath(self.class.base_xpath) do |result|
|
229
|
+
{
|
230
|
+
:psp_reference => result.text('./payment:pspReference'),
|
231
|
+
:response => result.text('./payment:response')
|
232
|
+
}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class CaptureResponse < ModificationResponse
|
238
|
+
self.request_received_value = '[capture-received]'
|
239
|
+
self.base_xpath = '//payment:captureResponse/payment:captureResult'
|
240
|
+
end
|
241
|
+
|
242
|
+
class RefundResponse < ModificationResponse
|
243
|
+
self.request_received_value = '[refund-received]'
|
244
|
+
self.base_xpath = '//payment:refundResponse/payment:refundResult'
|
245
|
+
end
|
246
|
+
|
247
|
+
class CancelResponse < ModificationResponse
|
248
|
+
self.request_received_value = '[cancel-received]'
|
249
|
+
self.base_xpath = '//payment:cancelResponse/payment:cancelResult'
|
250
|
+
end
|
251
|
+
|
252
|
+
class CancelOrRefundResponse < ModificationResponse
|
253
|
+
self.request_received_value = '[cancelOrRefund-received]'
|
254
|
+
self.base_xpath = '//payment:cancelOrRefundResponse/payment:cancelOrRefundResult'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'adyen/api/simple_soap_client'
|
2
|
+
require 'adyen/api/templates/recurring_service'
|
3
|
+
|
4
|
+
module Adyen
|
5
|
+
module API
|
6
|
+
# This is the class that maps actions to Adyen’s Recurring SOAP service.
|
7
|
+
#
|
8
|
+
# It’s encouraged to use the shortcut methods on the {API} module, which abstracts away the
|
9
|
+
# difference between this service and the {PaymentService}. Henceforth, for extensive
|
10
|
+
# documentation you should look at the {API} documentation.
|
11
|
+
#
|
12
|
+
# The most important difference is that you instantiate a {RecurringService} with the parameters
|
13
|
+
# that are needed for the call that you will eventually make.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# recurring = Adyen::API::RecurringService.new(:shopper => { :reference => user.id })
|
17
|
+
# response = recurring.disable
|
18
|
+
# response.success? # => true
|
19
|
+
#
|
20
|
+
class RecurringService < SimpleSOAPClient
|
21
|
+
# The Adyen Recurring SOAP service endpoint uri.
|
22
|
+
ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Recurring'
|
23
|
+
|
24
|
+
# @see API.list_recurring_details
|
25
|
+
def list
|
26
|
+
call_webservice_action('listRecurringDetails', list_request_body, ListResponse)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see API.disable_recurring_contract
|
30
|
+
def disable
|
31
|
+
call_webservice_action('disable', disable_request_body, DisableResponse)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def list_request_body
|
37
|
+
validate_parameters!(:merchant_account, :shopper => [:reference])
|
38
|
+
LIST_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference]]
|
39
|
+
end
|
40
|
+
|
41
|
+
def disable_request_body
|
42
|
+
validate_parameters!(:merchant_account, :shopper => [:reference])
|
43
|
+
if reference = @params[:recurring_detail_reference]
|
44
|
+
reference = RECURRING_DETAIL_PARTIAL % reference
|
45
|
+
end
|
46
|
+
DISABLE_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference], reference || '']
|
47
|
+
end
|
48
|
+
|
49
|
+
class DisableResponse < Response
|
50
|
+
DISABLED_RESPONSES = %w{ [detail-successfully-disabled] [all-details-successfully-disabled] }
|
51
|
+
|
52
|
+
response_attrs :response
|
53
|
+
|
54
|
+
def success?
|
55
|
+
super && DISABLED_RESPONSES.include?(params[:response])
|
56
|
+
end
|
57
|
+
|
58
|
+
alias disabled? success?
|
59
|
+
|
60
|
+
def params
|
61
|
+
@params ||= { :response => xml_querier.text('//recurring:disableResponse/recurring:result/recurring:response') }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ListResponse < Response
|
66
|
+
response_attrs :details, :last_known_shopper_email, :shopper_reference, :creation_date
|
67
|
+
|
68
|
+
def references
|
69
|
+
details.map { |d| d[:recurring_detail_reference] }
|
70
|
+
end
|
71
|
+
|
72
|
+
def params
|
73
|
+
@params ||= xml_querier.xpath('//recurring:listRecurringDetailsResponse/recurring:result') do |result|
|
74
|
+
details = result.xpath('.//recurring:RecurringDetail')
|
75
|
+
details.empty? ? {} : {
|
76
|
+
:creation_date => DateTime.parse(result.text('./recurring:creationDate')),
|
77
|
+
:details => details.map { |node| parse_recurring_detail(node) },
|
78
|
+
:last_known_shopper_email => result.text('./recurring:lastKnownShopperEmail'),
|
79
|
+
:shopper_reference => result.text('./recurring:shopperReference')
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# @todo add support for elv
|
87
|
+
def parse_recurring_detail(node)
|
88
|
+
result = {
|
89
|
+
:recurring_detail_reference => node.text('./recurring:recurringDetailReference'),
|
90
|
+
:variant => node.text('./recurring:variant'),
|
91
|
+
:creation_date => DateTime.parse(node.text('./recurring:creationDate'))
|
92
|
+
}
|
93
|
+
|
94
|
+
card = node.xpath('./recurring:card')
|
95
|
+
if card.children.empty?
|
96
|
+
result[:bank] = parse_bank_details(node.xpath('./recurring:bank'))
|
97
|
+
else
|
98
|
+
result[:card] = parse_card_details(card)
|
99
|
+
end
|
100
|
+
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_card_details(card)
|
105
|
+
{
|
106
|
+
:expiry_date => Date.new(card.text('./payment:expiryYear').to_i, card.text('./payment:expiryMonth').to_i, -1),
|
107
|
+
:holder_name => card.text('./payment:holderName'),
|
108
|
+
:number => card.text('./payment:number')
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse_bank_details(bank)
|
113
|
+
{
|
114
|
+
:bank_account_number => bank.text('./payment:bankAccountNumber'),
|
115
|
+
:bank_location_id => bank.text('./payment:bankLocationId'),
|
116
|
+
:bank_name => bank.text('./payment:bankName'),
|
117
|
+
:bic => bank.text('./payment:bic'),
|
118
|
+
:country_code => bank.text('./payment:countryCode'),
|
119
|
+
:iban => bank.text('./payment:iban'),
|
120
|
+
:owner_name => bank.text('./payment:ownerName')
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Adyen
|
2
|
+
module API
|
3
|
+
# The base class of all responses returned by API calls to Adyen.
|
4
|
+
class Response
|
5
|
+
# Defines shortcut accessor methods, to {Response#params}, for the given parameters.
|
6
|
+
def self.response_attrs(*attrs)
|
7
|
+
attrs.each do |attr|
|
8
|
+
define_method(attr) { params[attr] }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Net::HTTPResponse] The response object returned by Net::HTTP.
|
13
|
+
attr_reader :http_response
|
14
|
+
|
15
|
+
# @param [Net::HTTPResponse] http_response The response object returned by Net::HTTP.
|
16
|
+
def initialize(http_response)
|
17
|
+
@http_response = http_response
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String] The raw body of the response object.
|
21
|
+
def body
|
22
|
+
@http_response.body
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] Whether or not the request was successful.
|
26
|
+
def success?
|
27
|
+
!http_failure?
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean] Whether or not the HTTP request was a success.
|
31
|
+
def http_failure?
|
32
|
+
!@http_response.is_a?(Net::HTTPSuccess)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [XMLQuerier] The response body wrapped in a XMLQuerier.
|
36
|
+
def xml_querier
|
37
|
+
@xml_querier ||= XMLQuerier.new(@http_response.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Hash] Subclasses return the parsed response body.
|
41
|
+
def params
|
42
|
+
raise "The Adyen::API::Response#params method should be overridden in a subclass."
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String,nil] The SOAP failure message, if there is one.
|
46
|
+
def fault_message
|
47
|
+
@fault_message ||= begin
|
48
|
+
message = xml_querier.text('//soap:Fault/faultstring')
|
49
|
+
message unless message.empty?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
require 'adyen/api/response'
|
4
|
+
require 'adyen/api/xml_querier'
|
5
|
+
|
6
|
+
module Adyen
|
7
|
+
module API
|
8
|
+
# The base class of the API classes that map to Adyen SOAP services.
|
9
|
+
class SimpleSOAPClient
|
10
|
+
# @private
|
11
|
+
ENVELOPE = <<EOS
|
12
|
+
<?xml version="1.0"?>
|
13
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
14
|
+
<soap:Body>
|
15
|
+
%s
|
16
|
+
</soap:Body>
|
17
|
+
</soap:Envelope>
|
18
|
+
EOS
|
19
|
+
|
20
|
+
# A CA file used to verify certificates when connecting to Adyen.
|
21
|
+
#
|
22
|
+
# @see http://curl.haxx.se/ca/cacert.pem
|
23
|
+
CACERT = File.expand_path('../cacert.pem', __FILE__)
|
24
|
+
|
25
|
+
class ClientError < StandardError
|
26
|
+
def initialize(response, action, endpoint)
|
27
|
+
@response, @action, @endpoint = response, action, endpoint
|
28
|
+
end
|
29
|
+
|
30
|
+
def message
|
31
|
+
"[#{@response.code} #{@response.message}] A client error occurred while calling SOAP action `#{@action}' on endpoint `#{@endpoint}'."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# When a response instance has been assigned, the subsequent call to
|
37
|
+
# {SimpleSOAPClient#call_webservice_action} will not make a remote call, but simply return
|
38
|
+
# the stubbed response instance. This is obviously meant for making payments from tests.
|
39
|
+
#
|
40
|
+
# @see PaymentService::TestHelpers
|
41
|
+
# @see RecurringService::TestHelpers
|
42
|
+
#
|
43
|
+
# @return [Response] The stubbed Response subclass instance.
|
44
|
+
attr_accessor :stubbed_response
|
45
|
+
|
46
|
+
# @return [URI] A URI based on the ENDPOINT_URI constant defined on subclasses, where
|
47
|
+
# the environment type has been interpolated. E.g. Test environment.
|
48
|
+
def endpoint
|
49
|
+
@endpoint ||= URI.parse(const_get('ENDPOINT_URI') % Adyen.configuration.environment)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Hash] A hash of key-value pairs required for the action that is to be called.
|
54
|
+
attr_reader :params
|
55
|
+
|
56
|
+
# @param [Hash] params A hash of key-value pairs required for the action that is to be called.
|
57
|
+
# These are merged with the Adyen::API.default_params.
|
58
|
+
def initialize(params = {})
|
59
|
+
@params = Adyen.configuration.default_api_params.merge(params)
|
60
|
+
end
|
61
|
+
|
62
|
+
def validate_parameter_value!(param, value)
|
63
|
+
if value.nil? || value =~ /^\s*$/
|
64
|
+
raise ArgumentError, "The required parameter `:#{param}' is missing."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_parameters!(*params)
|
69
|
+
params.each do |param|
|
70
|
+
case param
|
71
|
+
when Symbol
|
72
|
+
validate_parameter_value!(param, @params[param])
|
73
|
+
when Hash
|
74
|
+
param.each do |name, attrs|
|
75
|
+
validate_parameter_value!(name, @params[name])
|
76
|
+
attrs.each { |attr| validate_parameter_value!("#{name} => :#{attr}", @params[name][attr]) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# This method wraps the given XML +data+ in a SOAP envelope and posts it to +action+ on the
|
83
|
+
# +endpoint+ defined for the subclass.
|
84
|
+
#
|
85
|
+
# The result is a response object, with XMLQuerier, ready to be queried.
|
86
|
+
#
|
87
|
+
# If a {stubbed_response} has been set, then said response is returned and no actual remote
|
88
|
+
# calls are made.
|
89
|
+
#
|
90
|
+
# @param [String] action The remote action to call.
|
91
|
+
# @param [String] data The XML data to post to the remote action.
|
92
|
+
# @param [Response] response_class The Response subclass used to wrap the response from Adyen.
|
93
|
+
def call_webservice_action(action, data, response_class)
|
94
|
+
if response = self.class.stubbed_response
|
95
|
+
self.class.stubbed_response = nil
|
96
|
+
response
|
97
|
+
else
|
98
|
+
endpoint = self.class.endpoint
|
99
|
+
|
100
|
+
post = Net::HTTP::Post.new(endpoint.path, 'Accept' => 'text/xml', 'Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => action)
|
101
|
+
post.basic_auth(Adyen.configuration.api_username, Adyen.configuration.api_password)
|
102
|
+
post.body = ENVELOPE % data
|
103
|
+
|
104
|
+
request = Net::HTTP.new(endpoint.host, endpoint.port)
|
105
|
+
request.use_ssl = true
|
106
|
+
request.ca_file = CACERT
|
107
|
+
request.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
108
|
+
|
109
|
+
request.start do |http|
|
110
|
+
http_response = http.request(post)
|
111
|
+
raise ClientError.new(http_response, action, endpoint) if http_response.is_a?(Net::HTTPClientError)
|
112
|
+
response_class.new(http_response)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|