class Docdata::Response

Object representing a “response” with attributes provided by Docdata

@example

:create_success=>{
  :success=>"Operation successful.", 
  :key=>"A7B623A3A7DB5949316F82049450C3F3"
}

Attributes

amount[RW]

@return [Integer] the captured amount in cents

currency[RW]

@return [String] Currency (“EUR”, “GBP”, “USD”, etc.)

key[RW]

@return [String] Payment key for future correspondence about this transaction

message[RW]

@return [String] Response message from DocData

paid[RW]

@return [Boolean]

paid?[RW]

@return [Boolean]

payment[RW]

@return [Docdata::Payment] object

report[RW]

@return [Hash] The parsed report node of the reponse-xml

status[RW]

@return [String] the status of this response (capture response)

success[RW]

@return [Boolean] true/false, depending of the API response

success?[RW]

@return [Boolean] true/false, depending of the API response

url[RW]

@return [String] the return URL

xml[RW]

@return [String] The raw XML returned by the API

Public Class Methods

new(args=nil) click to toggle source

Initializer to transform a Hash into an Response object

@param [Hash] args

# File lib/docdata/response.rb, line 53
def initialize(args=nil)
  @report = {}
  return if args.nil?
  args.each do |k,v|
    instance_variable_set("@#{k}", v) unless v.nil?
  end
end
parse(method_name, response) click to toggle source

Parses the returned response hash and turns it into a new Docdata::Response object

@param [String] method_name (name of the method: create, start, cancel, etc.) @param [Hash] response

# File lib/docdata/response.rb, line 90
def self.parse(method_name, response)
  body, xml = self.response_body(response)
  if body["#{method_name}_response".to_sym] && body["#{method_name}_response".to_sym]["#{method_name}_error".to_sym]
    raise DocdataError.new(response), body["#{method_name}_response".to_sym]["#{method_name}_error".to_sym][:error]
  else
    m = body["#{method_name}_response".to_sym]["#{method_name}_success".to_sym]
    r = self.new(key: m[:key], message: m[:success], success: true)
    r.xml    = xml #save the raw xml
    # puts m[:report]
    if m[:report]
      r.report = m[:report]
    end
    r.set_attributes
    return r
  end
end
response_body(response) click to toggle source

@return [Hash] the body of the response. In the test environment, this uses plain XML files, in normal use, it uses a ‘Savon::Response`

# File lib/docdata/response.rb, line 109
def self.response_body(response)
  if response.is_a?(File)
    parser = Nori.new(:convert_tags_to => lambda { |tag| tag.snakecase.to_sym })
    xml = response.read 
    body = parser.parse(xml).first.last.first.last
  else
    body = response.body.to_hash
    xml = response.xml
  end
  return body, xml
end

Private Class Methods

payment_node(hash) click to toggle source

Sometimes a single response has multiple payment nodes. When a payment fails first and succeeds later, for example. In that case, always use the newest (== highest id) node. UPDATE (2/3/2015) this is not always the case: a payment can receive a ‘canceled’ payment later on with a higher ID, but the order is still paid.

# File lib/docdata/response.rb, line 264
def self.payment_node(hash)
  if hash[:payment] && hash[:payment].is_a?(Hash)
    hash[:payment]
  elsif hash[:payment] && hash[:payment].is_a?(Array)
    # use the node with the highest ID, for it is the newest
    list = hash[:payment].sort_by { |k| k[:id] }
    return list.last
  else
    false
  end
end

Public Instance Methods

amount_to_set() click to toggle source
Integer

the amount to set, calculated from the multiple payment nodes

# File lib/docdata/response.rb, line 63
def amount_to_set
  #  if (report && Response.payment_node(report) && Response.payment_node(report)[:authorization] && Response.payment_node(report)[:authorization][:amount].present?)
  if (report && Response.payment_node(report) && Response.payment_node(report)[:authorization] && Response.payment_node(report)[:authorization][:amount].present?)
    if canceled
      return Response.payment_node(report)[:authorization][:amount].to_i
    else
      return total_acquirer_pending + total_acquirer_approved
    end
  else
    return false
  end
end
authorized() click to toggle source

@return [Boolean]

# File lib/docdata/response.rb, line 213
def authorized
  payment_status == "AUTHORIZED"
end
Also aliased as: authorized?
authorized?()
Alias for: authorized
canceled() click to toggle source

@return [Boolean]

# File lib/docdata/response.rb, line 219
def canceled
  (payment_status && payment_status == "CANCELED") || 
    (capture_status && capture_status == "CANCELED")
end
Also aliased as: canceled?
canceled?()
Alias for: canceled
capture_status() click to toggle source

@return [String] the status of the capture, if exists

# File lib/docdata/response.rb, line 226
def capture_status
  if report && Response.payment_node(report) && Response.payment_node(report)[:authorization] && Response.payment_node(report)[:authorization][:capture]
    Response.payment_node(report)[:authorization][:capture][:status]
  else
    nil
  end
end
currency_to_set() click to toggle source

@return [String] the currency if this transaction

# File lib/docdata/response.rb, line 236
def currency_to_set
  if status_xml &&  status_xml.xpath("//amount").any?
    status_xml.xpath("//amount").first.attributes["currency"].value
  else
    nil
  end
end
doc() click to toggle source

@return [Nokogiri::XML::Document] object

# File lib/docdata/response.rb, line 245
def doc
  # remove returns and whitespaces between tags
  xml_string = xml.gsub("\n", "").gsub(/>\s+</, "><")
  # return Nokogiri::XML::Document
  @doc ||= Nokogiri.XML(xml_string)
end
is_paid?() click to toggle source

@return [Boolean] true/false, depending wether this payment is considered paid. @note Docdata doesn’t explicitly say ‘paid’ or ‘not paid’, this is a little bit a gray area. There are several approaches to determine if a payment is paid, some slow and safe, other quick and unreliable. The reason for this is that some payment methods have a much longer processing time. For each payment method a different ‘paid’. @note This method is never 100% reliable. If you need to finetune this, please implement your own method, using the available data (total_captured, total_registered, etc.)

from the Docs:
 Safe route:

The safest route to check whether all payments were made is for the merchants to refer to the “Total captured” amount to see whether this equals the “Total registered amount”. While this may be the safest indicator, the downside is that it can sometimes take a long time for acquirers or shoppers to actually have the money transferred and it can be captured.

Quick route:

Another option is to see whether the sum of “total shopper pending”, “total acquirer pending” and “total acquirer authorized” matches the “total registered sum”. This implies that everyone responsible has indicated that they are going to make the payment and that the merchant is trusting that everyone will indeed make this. While this route will be faster, it does also have the risk that some payments will actually not have been made.

Balanced route:

Depending on the merchant’s situation, it can be a good option to only refer to certain totals. For instance, if the merchant only makes use of credit card payments it could be a good route to only look at “Total acquirer approved”, since this will be rather safe but quicker than looking at the captures.

# File lib/docdata/response.rb, line 185
def is_paid?

  if payment_method
    case payment_method
    # ideal (dutch)
    when "IDEAL" 
      (total_registered == total_captured) ## && (capture_status == "CAPTURED")
    # creditcard
    when "MASTERCARD", "VISA", "AMEX"
      (total_registered == total_acquirer_approved)
    # sofort überweisung (german)
    when "SOFORT_UEBERWEISUNG"
      (total_registered == total_acquirer_approved)
    # podium giftcard (dutch)
    when "PODIUM_GIFTCARD"
      (total_registered == total_captured)
    # fallback: if total_registered equals total_caputured,
    # we can assume that this order is paid. No 100% guarantee.
    else
      total_registered == total_acquirer_approved
    end
  else
    false
  end
end
payment_method() click to toggle source

@return [String] the payment method of this transaction

# File lib/docdata/response.rb, line 129
def payment_method
  begin
    if report && Response.payment_node(report).present? && Response.payment_node(report)[:payment_method].present?
      Response.payment_node(report)[:payment_method].to_s
    else
      nil
    end
  rescue
    nil
  end
end
payment_status() click to toggle source

@return [String] the status string provided by the API. One of [AUTHORIZED, CANCELED]

# File lib/docdata/response.rb, line 143
def payment_status
  if report && Response.payment_node(report) && Response.payment_node(report)[:authorization]
    Response.payment_node(report)[:authorization][:status]
  else
    nil
  end
end
pid() click to toggle source

@return [String] the PID of the transaction

# File lib/docdata/response.rb, line 152
def pid
  if report && Response.payment_node(report) && Response.payment_node(report)[:id]
    Response.payment_node(report)[:id]
  else
    nil
  end      
end
set_attributes() click to toggle source

Set the attributes based on the API response

# File lib/docdata/response.rb, line 77
def set_attributes
  self.paid     = is_paid?
  self.amount   = amount_to_set if amount_to_set
  self.status   = capture_status if capture_status
  self.currency = currency_to_set
end
status_xml() click to toggle source

@return [Nokogiri::XML::Document] object, containing only the status section @note This is a fix for Nokogiri’s trouble finding xpath elements after ‘xlmns’ attribute in a node.

# File lib/docdata/response.rb, line 254
def status_xml
  @status_xml ||= Nokogiri.XML(doc.xpath("//S:Body").first.children.first.children.first.to_xml)
end