lib/adyen/api.rb in fingertips-adyen-0.3.8.20100924 vs lib/adyen/api.rb in fingertips-adyen-0.3.8.20100929
- old
+ new
@@ -52,11 +52,11 @@
def initialize(params = {})
@params = API.default_params.merge(params)
end
- def call_webservice_action(action, data)
+ def call_webservice_action(action, data, response_class)
endpoint = self.class.endpoint
post = Net::HTTP::Post.new(endpoint.path, 'Accept' => 'text/xml', 'Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => action)
post.basic_auth(API.username, API.password)
post.body = data
@@ -65,18 +65,119 @@
request.use_ssl = true
request.ca_file = CACERT
request.verify_mode = OpenSSL::SSL::VERIFY_PEER
request.start do |http|
- response = http.request(post)
- # TODO: handle not 2xx responses
- #p response
- XMLQuerier.new(response.body)
+ response_class.new(http.request(post))
end
end
end
+ class Response
+ def self.response_attrs(*attrs)
+ attrs.each do |attr|
+ define_method(attr) { params[attr] }
+ end
+ end
+
+ attr_reader :http_response
+
+ def initialize(http_response)
+ @http_response = http_response
+ end
+
+ # @return [Boolean] Whether or not the request was successful.
+ def success?
+ !http_failure?
+ end
+
+ # @return [Boolean] Whether or not the HTTP request was a success.
+ def http_failure?
+ !@http_response.is_a?(Net::HTTPSuccess)
+ end
+
+ def xml_querier
+ @xml_querier ||= XMLQuerier.new(@http_response.body)
+ end
+
+ def params
+ raise "The Adyen::API::Response#params method should be overriden in a subclass."
+ end
+ end
+
+ class XMLQuerier
+ NS = {
+ 'soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'payment' => 'http://payment.services.adyen.com',
+ 'recurring' => 'http://recurring.services.adyen.com',
+ 'common' => 'http://common.services.adyen.com'
+ }
+
+ class << self
+ attr_accessor :backend
+
+ def backend=(backend)
+ @backend = backend
+ class_eval do
+ private
+ if backend == :nokogiri
+ def document_for_xml(xml)
+ Nokogiri::XML::Document.parse(xml)
+ end
+ def perform_xpath(query)
+ @node.xpath(query, NS)
+ end
+ else
+ def document_for_xml(xml)
+ REXML::Document.new(xml)
+ end
+ def perform_xpath(query)
+ REXML::XPath.match(@node, query, NS)
+ end
+ end
+ end
+ end
+ end
+
+ begin
+ require 'nokogiri'
+ self.backend = :nokogiri
+ rescue LoadError
+ require 'rexml/document'
+ self.backend = :rexml
+ end
+
+ def initialize(data)
+ @node = data.is_a?(String) ? document_for_xml(data) : data
+ end
+
+ def xpath(query)
+ result = self.class.new(perform_xpath(query))
+ block_given? ? yield(result) : result
+ end
+
+ def text(query)
+ xpath("#{query}/text()").to_s.strip
+ end
+
+ def children
+ @node.first.children
+ end
+
+ def empty?
+ @node.empty?
+ end
+
+ def to_s
+ @node.to_s
+ end
+
+ def map(&block)
+ @node.map { |n| self.class.new(n) }.map(&block)
+ end
+ end
+
class PaymentService < SimpleSOAPClient
ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Payment'
def authorise_payment
make_payment_request(authorise_payment_request_body)
@@ -87,19 +188,11 @@
end
private
def make_payment_request(data)
- response = call_webservice_action('authorise', data)
- response.xpath('//payment:authoriseResponse/payment:paymentResult') do |result|
- {
- :psp_reference => result.text('./payment:pspReference'),
- :result_code => result.text('./payment:resultCode'),
- :auth_code => result.text('./payment:authCode'),
- :refusal_reason => result.text('./payment:refusalReason')
- }
- end
+ call_webservice_action('authorise', data, AuthorizationResponse)
end
def authorise_payment_request_body
content = card_partial
content << RECURRING_PARTIAL if @params[:recurring]
@@ -128,31 +221,75 @@
end
def shopper_partial
@params[:shopper].map { |k, v| SHOPPER_PARTIALS[k] % v }.join("\n")
end
+
+ class AuthorizationResponse < Response
+ ERRORS = {
+ "validation 101 Invalid card number" => [:number, 'is not a valid creditcard number'],
+ "validation 103 CVC is not the right length" => [:cvc, 'is not the right length'],
+ "validation 128 Card Holder Missing" => [:holder_name, 'can’t be blank'],
+ "validation Couldn't parse expiry year" => [:expiry_year, 'could not be recognized'],
+ "validation Expiry month should be between 1 and 12 inclusive" => [:expiry_month, 'could not be recognized'],
+ }
+
+ AUTHORISED = 'Authorised'
+
+ def self.original_fault_message_for(attribute, message)
+ if error = ERRORS.find { |_, (a, m)| a == attribute && m == message }
+ error.first
+ else
+ message
+ end
+ end
+
+ response_attrs :result_code, :auth_code, :refusal_reason, :psp_reference
+
+ def success?
+ super && params[:result_code] == AUTHORISED
+ end
+
+ alias authorized? success?
+
+ def invalid_request?
+ !fault_message.nil?
+ end
+
+ def error
+ ERRORS[fault_message] || [:base, fault_message]
+ end
+
+ def params
+ @params ||= xml_querier.xpath('//payment:authoriseResponse/payment:paymentResult') do |result|
+ {
+ :psp_reference => result.text('./payment:pspReference'),
+ :result_code => result.text('./payment:resultCode'),
+ :auth_code => result.text('./payment:authCode'),
+ :refusal_reason => result.text('./payment:refusalReason')
+ }
+ end
+ end
+
+ private
+
+ def fault_message
+ @fault_message ||= xml_querier.text('//soap:Fault/faultstring')
+ end
+ end
end
class RecurringService < SimpleSOAPClient
ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Recurring'
# TODO: rename to list_details and make shortcut method take the only necessary param
def list
- response = call_webservice_action('listRecurringDetails', list_request_body)
- response.xpath('//recurring:listRecurringDetailsResponse/recurring:result') do |result|
- {
- :creation_date => DateTime.parse(result.text('./recurring:creationDate')),
- :details => result.xpath('.//recurring:RecurringDetail').map { |node| parse_recurring_detail(node) },
- :last_known_shopper_email => result.text('./recurring:lastKnownShopperEmail'),
- :shopper_reference => result.text('./recurring:shopperReference')
- }
- end
+ call_webservice_action('listRecurringDetails', list_request_body, ListResponse)
end
def disable
- response = call_webservice_action('disable', disable_request_body)
- { :response => response.text('//recurring:disableResponse/recurring:result/recurring:response') }
+ call_webservice_action('disable', disable_request_body, DisableResponse)
end
private
def list_request_body
@@ -164,118 +301,72 @@
reference = RECURRING_DETAIL_PARTIAL % reference
end
DISABLE_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference], reference || '']
end
- # @todo add support for elv
- def parse_recurring_detail(node)
- result = {
- :recurring_detail_reference => node.text('./recurring:recurringDetailReference'),
- :variant => node.text('./recurring:variant'),
- :creation_date => DateTime.parse(node.text('./recurring:creationDate'))
- }
+ class DisableResponse < Response
+ response_attrs :response
- card = node.xpath('./recurring:card')
- if card.children.empty?
- result[:bank] = parse_bank_details(node.xpath('./recurring:bank'))
- else
- result[:card] = parse_card_details(card)
+ def params
+ @params ||= { :response => xml_querier.text('//recurring:disableResponse/recurring:result/recurring:response') }
end
-
- result
end
- def parse_card_details(card)
- {
- :expiry_date => Date.new(card.text('./payment:expiryYear').to_i, card.text('./payment:expiryMonth').to_i, -1),
- :holder_name => card.text('./payment:holderName'),
- :number => card.text('./payment:number')
- }
- end
+ class ListResponse < Response
+ response_attrs :details, :last_known_shopper_email, :shopper_reference, :creation_date
- def parse_bank_details(bank)
- {
- :bank_account_number => bank.text('./payment:bankAccountNumber'),
- :bank_location_id => bank.text('./payment:bankLocationId'),
- :bank_name => bank.text('./payment:bankName'),
- :bic => bank.text('./payment:bic'),
- :country_code => bank.text('./payment:countryCode'),
- :iban => bank.text('./payment:iban'),
- :owner_name => bank.text('./payment:ownerName')
- }
- end
- end
-
- class XMLQuerier
- NS = {
- 'payment' => 'http://payment.services.adyen.com',
- 'recurring' => 'http://recurring.services.adyen.com',
- 'common' => 'http://common.services.adyen.com'
- }
-
- class << self
- attr_accessor :backend
-
- def backend=(backend)
- @backend = backend
- class_eval do
- private
- if backend == :nokogiri
- def document_for_xml(xml)
- Nokogiri::XML::Document.parse(xml)
- end
- def perform_xpath(query)
- @node.xpath(query, NS)
- end
- else
- def document_for_xml(xml)
- REXML::Document.new(xml)
- end
- def perform_xpath(query)
- REXML::XPath.match(@node, query, NS)
- end
- end
+ def params
+ @params ||= xml_querier.xpath('//recurring:listRecurringDetailsResponse/recurring:result') do |result|
+ {
+ :creation_date => DateTime.parse(result.text('./recurring:creationDate')),
+ :details => result.xpath('.//recurring:RecurringDetail').map { |node| parse_recurring_detail(node) },
+ :last_known_shopper_email => result.text('./recurring:lastKnownShopperEmail'),
+ :shopper_reference => result.text('./recurring:shopperReference')
+ }
end
end
- end
- begin
- require 'nokogiri'
- self.backend = :nokogiri
- rescue LoadError
- require 'rexml/document'
- self.backend = :rexml
- end
+ private
- def initialize(data)
- @node = data.is_a?(String) ? document_for_xml(data) : data
- end
+ # @todo add support for elv
+ def parse_recurring_detail(node)
+ result = {
+ :recurring_detail_reference => node.text('./recurring:recurringDetailReference'),
+ :variant => node.text('./recurring:variant'),
+ :creation_date => DateTime.parse(node.text('./recurring:creationDate'))
+ }
- def xpath(query)
- result = self.class.new(perform_xpath(query))
- block_given? ? yield(result) : result
- end
+ card = node.xpath('./recurring:card')
+ if card.children.empty?
+ result[:bank] = parse_bank_details(node.xpath('./recurring:bank'))
+ else
+ result[:card] = parse_card_details(card)
+ end
- def text(query)
- xpath("#{query}/text()").to_s.strip
- end
+ result
+ end
- def children
- @node.first.children
- end
+ def parse_card_details(card)
+ {
+ :expiry_date => Date.new(card.text('./payment:expiryYear').to_i, card.text('./payment:expiryMonth').to_i, -1),
+ :holder_name => card.text('./payment:holderName'),
+ :number => card.text('./payment:number')
+ }
+ end
- def empty?
- @node.empty?
+ def parse_bank_details(bank)
+ {
+ :bank_account_number => bank.text('./payment:bankAccountNumber'),
+ :bank_location_id => bank.text('./payment:bankLocationId'),
+ :bank_name => bank.text('./payment:bankName'),
+ :bic => bank.text('./payment:bic'),
+ :country_code => bank.text('./payment:countryCode'),
+ :iban => bank.text('./payment:iban'),
+ :owner_name => bank.text('./payment:ownerName')
+ }
+ end
end
-
- def to_s
- @node.to_s
- end
-
- def map(&block)
- @node.map { |n| self.class.new(n) }.map(&block)
- end
end
end
end
########################
@@ -289,90 +380,93 @@
class PaymentService
LAYOUT = <<EOS
<?xml version="1.0"?>
<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">
<soap:Body>
- <ns1:authorise xmlns:ns1="http://payment.services.adyen.com">
- <ns1:paymentRequest>
- <merchantAccount xmlns="http://payment.services.adyen.com">%s</merchantAccount>
- <reference xmlns="http://payment.services.adyen.com">%s</reference>
+ <payment:authorise xmlns:payment="http://payment.services.adyen.com" xmlns:recurring="http://recurring.services.adyen.com" xmlns:common="http://common.services.adyen.com">
+ <payment:paymentRequest>
+ <payment:merchantAccount>%s</payment:merchantAccount>
+ <payment:reference>%s</payment:reference>
%s
- </ns1:paymentRequest>
- </ns1:authorise>
+ </payment:paymentRequest>
+ </payment:authorise>
</soap:Body>
</soap:Envelope>
EOS
AMOUNT_PARTIAL = <<EOS
- <amount xmlns="http://payment.services.adyen.com">
- <currency xmlns="http://common.services.adyen.com">%s</currency>
- <value xmlns="http://common.services.adyen.com">%s</value>
- </amount>
+ <payment:amount>
+ <common:currency>%s</common:currency>
+ <common:value>%s</common:value>
+ </payment:amount>
EOS
CARD_PARTIAL = <<EOS
- <card xmlns="http://payment.services.adyen.com">
- <holderName>%s</holderName>
- <number>%s</number>
- <cvc>%s</cvc>
- <expiryYear>%s</expiryYear>
- <expiryMonth>%02d</expiryMonth>
- </card>
+ <payment:card>
+ <payment:holderName>%s</payment:holderName>
+ <payment:number>%s</payment:number>
+ <payment:cvc>%s</payment:cvc>
+ <payment:expiryYear>%s</payment:expiryYear>
+ <payment:expiryMonth>%02d</payment:expiryMonth>
+ </payment:card>
EOS
RECURRING_PARTIAL = <<EOS
- <recurring xmlns="http://recurring.services.adyen.com">
- <contract xmlns="http://payment.services.adyen.com">RECURRING</contract>
- </recurring>
+ <payment:recurring>
+ <payment:contract>RECURRING</payment:contract>
+ </payment:recurring>
EOS
- RECURRING_PAYMENT_BODY_PARTIAL = RECURRING_PARTIAL + <<EOS
- <ns1:selectedRecurringDetailReference>%s</ns1:selectedRecurringDetailReference>
- <ns1:shopperInteraction>ContAuth</ns1:shopperInteraction>
+ RECURRING_PAYMENT_BODY_PARTIAL = <<EOS
+ <payment:recurring>
+ <payment:contract>RECURRING</payment:contract>
+ </payment:recurring>
+ <payment:selectedRecurringDetailReference>%s</payment:selectedRecurringDetailReference>
+ <payment:shopperInteraction>ContAuth</payment:shopperInteraction>
EOS
SHOPPER_PARTIALS = {
- :reference => ' <shopperReference xmlns="http://payment.services.adyen.com">%s</shopperReference>',
- :email => ' <shopperEmail xmlns="http://payment.services.adyen.com">%s</shopperEmail>',
- :ip => ' <shopperIP xmlns="http://payment.services.adyen.com">%s</shopperIP>',
+ :reference => ' <payment:shopperReference>%s</payment:shopperReference>',
+ :email => ' <payment:shopperEmail>%s</payment:shopperEmail>',
+ :ip => ' <payment:shopperIP>%s</payment:shopperIP>',
}
end
class RecurringService
LIST_LAYOUT = <<EOS
<?xml version="1.0"?>
<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">
<soap:Body>
- <ns1:listRecurringDetails xmlns:ns1="http://recurring.services.adyen.com">
- <ns1:request>
- <ns1:recurring>
- <ns1:contract>RECURRING</ns1:contract>
- </ns1:recurring>
- <ns1:merchantAccount>%s</ns1:merchantAccount>
- <ns1:shopperReference>%s</ns1:shopperReference>
- </ns1:request>
- </ns1:listRecurringDetails>
+ <recurring:listRecurringDetails xmlns:recurring="http://recurring.services.adyen.com">
+ <recurring:request>
+ <recurring:recurring>
+ <recurring:contract>RECURRING</recurring:contract>
+ </recurring:recurring>
+ <recurring:merchantAccount>%s</recurring:merchantAccount>
+ <recurring:shopperReference>%s</recurring:shopperReference>
+ </recurring:request>
+ </recurring:listRecurringDetails>
</soap:Body>
</soap:Envelope>
EOS
DISABLE_LAYOUT = <<EOS
<?xml version="1.0"?>
<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">
<soap:Body>
- <ns1:disable xmlns:ns1="http://recurring.services.adyen.com">
- <ns1:request>
- <ns1:merchantAccount>%s</ns1:merchantAccount>
- <ns1:shopperReference>%s</ns1:shopperReference>
+ <recurring:disable xmlns:recurring="http://recurring.services.adyen.com">
+ <recurring:request>
+ <recurring:merchantAccount>%s</recurring:merchantAccount>
+ <recurring:shopperReference>%s</recurring:shopperReference>
%s
- </ns1:request>
- </ns1:disable>
+ </recurring:request>
+ </recurring:disable>
</soap:Body>
</soap:Envelope>
EOS
RECURRING_DETAIL_PARTIAL = <<EOS
- <ns1:recurringDetailReference>%s</ns1:recurringDetailReference>
+ <recurring:recurringDetailReference>%s</recurring:recurringDetailReference>
EOS
end
end
end