docdata 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +2 -0
- data/.gitignore +4 -1
- data/.travis.yml +10 -0
- data/LICENSE +1 -1
- data/README.md +173 -7
- data/Rakefile +8 -0
- data/docdata.gemspec +18 -11
- data/lib/docdata.rb +74 -1
- data/lib/docdata/bank.rb +27 -0
- data/lib/docdata/config.rb +41 -0
- data/lib/docdata/docdata_error.rb +8 -0
- data/lib/docdata/engine.rb +13 -0
- data/lib/docdata/ideal.rb +40 -0
- data/lib/docdata/line_item.rb +99 -0
- data/lib/docdata/payment.rb +196 -0
- data/lib/docdata/response.rb +173 -0
- data/lib/docdata/shopper.rb +112 -0
- data/lib/docdata/version.rb +1 -1
- data/lib/docdata/xml/bank-list.xml +39 -0
- data/lib/docdata/xml/cancel.xml.erb +9 -0
- data/lib/docdata/xml/create.xml.erb +98 -0
- data/lib/docdata/xml/start.xml.erb +67 -0
- data/lib/docdata/xml/status.xml.erb +9 -0
- data/php-example/create.xml.erb +140 -0
- data/php-example/index.html +78 -0
- data/php-example/process.php +182 -0
- data/php-example/return.php +36 -0
- data/php-example/soap.rb +21 -0
- data/spec/config_spec.rb +53 -0
- data/spec/ideal_spec.rb +19 -0
- data/spec/line_item_spec.rb +55 -0
- data/spec/payment_spec.rb +162 -0
- data/spec/response_spec.rb +206 -0
- data/spec/shopper_spec.rb +50 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/xml/status-canceled-creditcard.xml +34 -0
- data/spec/xml/status-canceled-ideal.xml +29 -0
- data/spec/xml/status-new.xml +20 -0
- data/spec/xml/status-paid-creditcard.xml +33 -0
- data/spec/xml/status-paid-ideal.xml +33 -0
- data/spec/xml/status-paid-sofort.xml +33 -0
- metadata +145 -13
- data/LICENSE.txt +0 -22
@@ -0,0 +1,13 @@
|
|
1
|
+
module Docdata
|
2
|
+
#
|
3
|
+
# Simpel extend on the +Rails::Engine+ to add support for a new config section within
|
4
|
+
# the environment configs
|
5
|
+
#
|
6
|
+
# @example default
|
7
|
+
# # /config/environments/development.rb
|
8
|
+
# config.ideal_mollie.partner_id = 123456
|
9
|
+
#
|
10
|
+
class Engine < Rails::Engine
|
11
|
+
config.docdata = Docdata
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Docdata
|
2
|
+
#
|
3
|
+
# This class bundles all the needed logic and methods for IDEAL specific stuff.
|
4
|
+
#
|
5
|
+
class Ideal
|
6
|
+
|
7
|
+
#
|
8
|
+
# List of supported banks.
|
9
|
+
#
|
10
|
+
# @visibility public
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Docdata.banks
|
14
|
+
#
|
15
|
+
# For the lack of an available list of banks by Docdata,
|
16
|
+
# this gem uses the list provided by competitor Mollie.
|
17
|
+
#
|
18
|
+
# @return [Array<Docdata::Ideal>] list of supported +Bank+'s.
|
19
|
+
def self.banks
|
20
|
+
begin
|
21
|
+
@source ||= open('https://secure.mollie.nl/xml/ideal?a=banklist')
|
22
|
+
rescue
|
23
|
+
# in case the mollie API isn't available
|
24
|
+
# use the cached version (august 2014) of the XML file
|
25
|
+
@source = open("#{File.dirname(__FILE__)}/xml/bank-list.xml")
|
26
|
+
end
|
27
|
+
@response ||= Nokogiri::XML(@source)
|
28
|
+
@list = []
|
29
|
+
@response.xpath("//bank").each do |b|
|
30
|
+
bank = Docdata::Bank.new(
|
31
|
+
id: b.xpath("bank_id").first.content,
|
32
|
+
name: b.xpath("bank_name").first.content
|
33
|
+
)
|
34
|
+
@list << bank
|
35
|
+
end
|
36
|
+
return @list
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Docdata
|
2
|
+
|
3
|
+
|
4
|
+
# Creates a validator
|
5
|
+
class LineItemValidator
|
6
|
+
include Veto.validator
|
7
|
+
|
8
|
+
validates :name, presence: true
|
9
|
+
validates :quantity, presence: true, integer: true
|
10
|
+
validates :price_per_unit, presence: true, integer: true
|
11
|
+
validates :description, presence: true
|
12
|
+
validates :code, presence: true
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
#
|
17
|
+
# Object representing a "LineItem"
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# LineItem.new({
|
21
|
+
# :name => "Ham and Eggs by dr. Seuss",
|
22
|
+
# :code => "EAN312313235",
|
23
|
+
# :quantity => 1,
|
24
|
+
# :description => "The famous childrens book",
|
25
|
+
# :image => "http://blogs.slj.com/afuse8production/files/2012/06/GreenEggsHam1.jpg",
|
26
|
+
# :price_per_unit => 1299,
|
27
|
+
# :vat_rate => 17.5,
|
28
|
+
# :vat_included => true
|
29
|
+
# })
|
30
|
+
# @note Warning: do not use this part of the gem, for it will break. Be warned!
|
31
|
+
class LineItem
|
32
|
+
attr_accessor :errors
|
33
|
+
attr_accessor :name
|
34
|
+
attr_accessor :code
|
35
|
+
attr_accessor :quantity
|
36
|
+
attr_accessor :unit_of_measure
|
37
|
+
attr_accessor :description
|
38
|
+
attr_accessor :image
|
39
|
+
attr_accessor :price_per_unit
|
40
|
+
attr_accessor :vat_rate
|
41
|
+
attr_accessor :vat_included
|
42
|
+
|
43
|
+
#
|
44
|
+
# Initializer to transform a +Hash+ into an LineItem object
|
45
|
+
#
|
46
|
+
# @param [Hash] args
|
47
|
+
def initialize(args=nil)
|
48
|
+
@unit_of_measure = "PCS"
|
49
|
+
@vat_rate = 0
|
50
|
+
@vat_included = true
|
51
|
+
return if args.nil?
|
52
|
+
args.each do |k,v|
|
53
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean] true/false, depending if this instanciated object is valid
|
58
|
+
def valid?
|
59
|
+
validator = LineItemValidator.new
|
60
|
+
validator.valid?(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String] the string that contains all the errors for this line_item
|
64
|
+
def error_message
|
65
|
+
"One of your line_items is invalid. Error messages: #{errors.full_messages.join(', ')}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Integer] total price of this line item
|
69
|
+
def total_price
|
70
|
+
price_per_unit * quantity
|
71
|
+
end
|
72
|
+
|
73
|
+
def gross_amount
|
74
|
+
if vat_included
|
75
|
+
total_price
|
76
|
+
else
|
77
|
+
total_price + vat
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def nett_amount
|
82
|
+
if vat_included
|
83
|
+
total_price - vat
|
84
|
+
else
|
85
|
+
total_vat
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Integer] the total amount of VAT (in cents) that is applicable for this line item,
|
90
|
+
# based on the vat_rate, quantity and price_per_unit
|
91
|
+
def vat
|
92
|
+
if vat_included
|
93
|
+
((gross_amount.to_f * "1.#{vat_rate.to_s.gsub('.','')}".to_f) - gross_amount) * -1
|
94
|
+
else
|
95
|
+
((nett_amount.to_f * "1.#{vat_rate.to_s.gsub('.','')}".to_f) - nett_amount)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module Docdata
|
2
|
+
|
3
|
+
# Creates a validator
|
4
|
+
class PaymentValidator
|
5
|
+
include Veto.validator
|
6
|
+
validates :amount, presence: true, integer: true
|
7
|
+
validates :profile, presence: true
|
8
|
+
validates :currency, presence: true, format: /[A-Z]{3}/
|
9
|
+
validates :order_reference, presence: true
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
#
|
15
|
+
# Object representing a "WSDL" object with attributes provided by Docdata
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Payment.new({
|
19
|
+
# :amount => 2500,
|
20
|
+
# :currency => "EUR",
|
21
|
+
# :order_reference => "TJ123"
|
22
|
+
# :profile => "MyProfile"
|
23
|
+
# :shopper => @shopper
|
24
|
+
# })
|
25
|
+
#
|
26
|
+
# @return [Array] Errors
|
27
|
+
# @param :amount [Integer] The total price in cents
|
28
|
+
# @param :currency [String] ISO currency code (USD, EUR, GBP, etc.)
|
29
|
+
# @param :order_reference [String] A unique order reference
|
30
|
+
# @param :profile [String] The DocData payment profile (e.g. 'MyProfile')
|
31
|
+
# @param :description [String] Description for this payment
|
32
|
+
# @param :receipt_text [String] A receipt text
|
33
|
+
# @param :shopper [Docdata::Shopper] A shopper object (instance of Docdata::Shopper)
|
34
|
+
# @param :bank_id [String] (optional) in case you want to redirect the consumer
|
35
|
+
# directly to the bank page (iDeal), you can set the bank id ('0031' for ABN AMRO for example.)
|
36
|
+
# @param :prefered_payment_method [String] (optional) set a prefered payment method.
|
37
|
+
# any of: [IDEAL, AMAX, VISA, etc.]
|
38
|
+
# @param :line_items [Array] (optional) Array of objects of type Docdata::LineItem
|
39
|
+
# @param :default_act [Boolean] (optional) Should the redirect URL contain a default_act=true parameter?
|
40
|
+
#
|
41
|
+
class Payment
|
42
|
+
attr_accessor :errors
|
43
|
+
attr_accessor :amount
|
44
|
+
@@amount = "?"
|
45
|
+
attr_accessor :description
|
46
|
+
attr_accessor :receipt_text
|
47
|
+
attr_accessor :currency
|
48
|
+
attr_accessor :order_reference
|
49
|
+
attr_accessor :profile
|
50
|
+
attr_accessor :shopper
|
51
|
+
attr_accessor :bank_id
|
52
|
+
attr_accessor :prefered_payment_method
|
53
|
+
attr_accessor :line_items
|
54
|
+
attr_accessor :key
|
55
|
+
attr_accessor :default_act
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
# Initializer to transform a +Hash+ into an Payment object
|
61
|
+
#
|
62
|
+
# @param [Hash] args
|
63
|
+
def initialize(args=nil)
|
64
|
+
@line_items = []
|
65
|
+
return if args.nil?
|
66
|
+
args.each do |k,v|
|
67
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# @return [Boolean] true/false, depending if this instanciated object is valid
|
73
|
+
def valid?
|
74
|
+
validator = PaymentValidator.new
|
75
|
+
validator.valid?(self)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# This is the most importent method. It uses all the attributes
|
80
|
+
# and performs a `create` action on Docdata Payments SOAP API.
|
81
|
+
# @return [Docdata::Response] response object with `key`, `message` and `success?` methods
|
82
|
+
#
|
83
|
+
def create
|
84
|
+
# if there are any line items, they should all be valid.
|
85
|
+
validate_line_items
|
86
|
+
|
87
|
+
# puts
|
88
|
+
|
89
|
+
# make the SOAP API call
|
90
|
+
response = Docdata.client.call(:create, xml: xml)
|
91
|
+
response_object = Docdata::Response.parse(:create, response)
|
92
|
+
if response_object.success?
|
93
|
+
self.key = response_object.key
|
94
|
+
end
|
95
|
+
return response_object
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [String] the xml to send in the SOAP API
|
99
|
+
def xml
|
100
|
+
xml_file = "#{File.dirname(__FILE__)}/xml/create.xml.erb"
|
101
|
+
template = File.read(xml_file)
|
102
|
+
namespace = OpenStruct.new(payment: self, shopper: shopper)
|
103
|
+
xml = ERB.new(template).result(namespace.instance_eval { binding })
|
104
|
+
end
|
105
|
+
|
106
|
+
# Initialize a Payment object with the key set
|
107
|
+
def self.find(api_key)
|
108
|
+
p = self.new(key: api_key)
|
109
|
+
if p.status.success
|
110
|
+
return p
|
111
|
+
else
|
112
|
+
raise DocdataError.new(p), p.status.message
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# This is one of the other native SOAP API methods.
|
118
|
+
# @return [Docdata::Response]
|
119
|
+
def status
|
120
|
+
# read the xml template
|
121
|
+
xml_file = "#{File.dirname(__FILE__)}/xml/status.xml.erb"
|
122
|
+
template = File.read(xml_file)
|
123
|
+
namespace = OpenStruct.new(payment: self)
|
124
|
+
xml = ERB.new(template).result(namespace.instance_eval { binding })
|
125
|
+
|
126
|
+
# puts xml
|
127
|
+
|
128
|
+
response = Docdata.client.call(:status, xml: xml)
|
129
|
+
response_object = Docdata::Response.parse(:status, response)
|
130
|
+
|
131
|
+
return response_object # Docdata::Response
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [String] The URI where the consumer can be redirected to in order to pay
|
135
|
+
def redirect_url
|
136
|
+
url = {}
|
137
|
+
|
138
|
+
base_url = Docdata.return_url
|
139
|
+
if Docdata.test_mode
|
140
|
+
redirect_base_url = 'https://test.docdatapayments.com/ps/menu'
|
141
|
+
else
|
142
|
+
redirect_base_url = 'https://secure.docdatapayments.com/ps/menu'
|
143
|
+
end
|
144
|
+
url[:command] = "show_payment_cluster"
|
145
|
+
url[:payment_cluster_key] = key
|
146
|
+
url[:merchant_name] = Docdata.username
|
147
|
+
# only include return URL if present
|
148
|
+
if base_url.present?
|
149
|
+
url[:return_url_success] = "#{base_url}/success?key=#{url[:payment_cluster_key]}"
|
150
|
+
url[:return_url_pending] = "#{base_url}/pending?key=#{url[:payment_cluster_key]}"
|
151
|
+
url[:return_url_canceled] = "#{base_url}/canceled?key=#{url[:payment_cluster_key]}"
|
152
|
+
url[:return_url_error] = "#{base_url}/error?key=#{url[:payment_cluster_key]}"
|
153
|
+
end
|
154
|
+
url[:client_language] = shopper.language_code
|
155
|
+
if default_act
|
156
|
+
url[:default_act] = true
|
157
|
+
end
|
158
|
+
if bank_id.present?
|
159
|
+
url[:ideal_issuer_id] = bank_id
|
160
|
+
url[:default_pm] = "IDEAL"
|
161
|
+
end
|
162
|
+
params = URI.encode_www_form(url)
|
163
|
+
uri = "#{redirect_base_url}?#{params}"
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
# In case there are any line_items, validate them all and
|
170
|
+
# raise an error for the first invalid LineItem
|
171
|
+
def validate_line_items
|
172
|
+
if @line_items.any?
|
173
|
+
for line_item in @line_items
|
174
|
+
if line_item.valid?
|
175
|
+
# do nothing, this line_item seems okay
|
176
|
+
else
|
177
|
+
raise DocdataError.new(line_item), line_item.error_message
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# @return [Hash] list of VAT-rates and there respective totals
|
184
|
+
def vat_rates
|
185
|
+
rates = {}
|
186
|
+
for item in @line_items
|
187
|
+
rates["vat_#{item.vat_rate.to_s}"] ||= {}
|
188
|
+
rates["vat_#{item.vat_rate.to_s}"][:rate] ||= item.vat_rate
|
189
|
+
rates["vat_#{item.vat_rate.to_s}"][:total] ||= 0
|
190
|
+
rates["vat_#{item.vat_rate.to_s}"][:total] += item.vat
|
191
|
+
end
|
192
|
+
return rates
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Docdata
|
2
|
+
|
3
|
+
#
|
4
|
+
# Object representing a "response" with attributes provided by Docdata
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# :create_success=>{
|
8
|
+
# :success=>"Operation successful.",
|
9
|
+
# :key=>"A7B623A3A7DB5949316F82049450C3F3"
|
10
|
+
# }
|
11
|
+
class Response
|
12
|
+
|
13
|
+
# @return [String] Payment key for future correspondence about this transaction
|
14
|
+
attr_accessor :key
|
15
|
+
# @return [Boolean] true/false, depending of the API response
|
16
|
+
attr_accessor :success
|
17
|
+
@@success = false
|
18
|
+
alias_method :success?, :success
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
# @return [String] Response message from DocData
|
23
|
+
attr_accessor :message
|
24
|
+
|
25
|
+
# @return [Hash] The parsed report node of the reponse-xml
|
26
|
+
attr_accessor :report
|
27
|
+
|
28
|
+
# @return [String] The raw XML returned by the API
|
29
|
+
attr_accessor :xml
|
30
|
+
|
31
|
+
|
32
|
+
#
|
33
|
+
# Initializer to transform a +Hash+ into an Response object
|
34
|
+
#
|
35
|
+
# @param [Hash] args
|
36
|
+
def initialize(args=nil)
|
37
|
+
@report = {}
|
38
|
+
return if args.nil?
|
39
|
+
args.each do |k,v|
|
40
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
#
|
46
|
+
# Parses the returned response hash and turns it
|
47
|
+
# into a new Docdata::Response object
|
48
|
+
#
|
49
|
+
# @param [String] method_name (name of the method: create, start, cancel, etc.)
|
50
|
+
# @param [Hash] response
|
51
|
+
def self.parse(method_name, response)
|
52
|
+
body, xml = self.response_body(response)
|
53
|
+
if body["#{method_name}_response".to_sym] && body["#{method_name}_response".to_sym]["#{method_name}_error".to_sym]
|
54
|
+
raise DocdataError.new(response), body["#{method_name}_response".to_sym]["#{method_name}_error".to_sym][:error]
|
55
|
+
else
|
56
|
+
m = body["#{method_name}_response".to_sym]["#{method_name}_success".to_sym]
|
57
|
+
r = self.new(key: m[:key], message: m[:success], success: true)
|
58
|
+
r.xml = xml #save the raw xml
|
59
|
+
if m[:report]
|
60
|
+
r.report = m[:report]
|
61
|
+
end
|
62
|
+
return r
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Hash] the body of the response. In the test environment, this uses
|
67
|
+
# plain XML files, in normal use, it uses a `Savon::Response`
|
68
|
+
def self.response_body(response)
|
69
|
+
if response.is_a?(File)
|
70
|
+
parser = Nori.new(:convert_tags_to => lambda { |tag| tag.snakecase.to_sym })
|
71
|
+
xml = response.read
|
72
|
+
body = parser.parse(xml).first.last.first.last
|
73
|
+
else
|
74
|
+
body = response.body.to_hash
|
75
|
+
xml = response.xml
|
76
|
+
end
|
77
|
+
return body, xml
|
78
|
+
end
|
79
|
+
|
80
|
+
methods = [:total_registered, :total_shopper_pending, :total_acquier_pending, :total_acquirer_approved, :total_captured, :total_refunded, :total_chargedback]
|
81
|
+
methods.each do |method|
|
82
|
+
define_method method do
|
83
|
+
report[:approximate_totals][method].to_i
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String] the payment method of this transaction
|
88
|
+
def payment_method
|
89
|
+
if report[:payment]
|
90
|
+
report[:payment][:payment_method]
|
91
|
+
else
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [String] the status string provided by the API. One of [AUTHORIZED, CANCELED]
|
97
|
+
def payment_status
|
98
|
+
report[:payment][:authorization][:status]
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Boolean] true/false, depending wether this payment is considered paid.
|
102
|
+
# @note Docdata doesn't explicitly say 'paid' or 'not paid', this is a little bit a gray area.
|
103
|
+
# There are several approaches to determine if a payment is paid, some slow and safe, other quick and unreliable.
|
104
|
+
# The reason for this is that some payment methods have a much longer processing time. For each payment method
|
105
|
+
# a different 'paid'.
|
106
|
+
# @note This method is never 100% reliable. If you need to finetune this, please implement your own method, using
|
107
|
+
# the available data (total_captured, total_registered, etc.)
|
108
|
+
def paid
|
109
|
+
if payment_method
|
110
|
+
case payment_method
|
111
|
+
# ideal (dutch)
|
112
|
+
when "IDEAL"
|
113
|
+
(total_registered == total_captured) && (capture_status == "CAPTURED")
|
114
|
+
# creditcard
|
115
|
+
when "MASTERCARD", "VISA", "AMEX"
|
116
|
+
(total_registered == total_acquirer_approved)
|
117
|
+
# sofort überweisung (german)
|
118
|
+
when "SOFORT_UEBERWEISUNG"
|
119
|
+
(total_registered == total_acquirer_approved)
|
120
|
+
# fallback: if total_registered equals total_caputured,
|
121
|
+
# we can assume that this order is paid. No 100% guarantee.
|
122
|
+
else
|
123
|
+
total_registered == total_captured
|
124
|
+
end
|
125
|
+
else
|
126
|
+
false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
alias_method :paid?, :paid
|
130
|
+
|
131
|
+
# @return [Boolean]
|
132
|
+
def authorized
|
133
|
+
payment_status == "AUTHORIZED"
|
134
|
+
end
|
135
|
+
alias_method :authorized?, :authorized
|
136
|
+
|
137
|
+
# @return [Boolean]
|
138
|
+
def canceled
|
139
|
+
payment_status == "CANCELED" || capture_status == "CANCELED"
|
140
|
+
end
|
141
|
+
alias_method :canceled?, :canceled
|
142
|
+
|
143
|
+
# @return [String] the status of the capture, if exists
|
144
|
+
def capture_status
|
145
|
+
report[:payment][:authorization][:capture][:status]
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [Integer] the caputred amount in cents
|
149
|
+
def amount
|
150
|
+
report[:payment][:authorization][:amount].to_i
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [String] the currency if this transaction
|
154
|
+
def currency
|
155
|
+
status_xml.xpath("//amount").first.attributes["currency"].value
|
156
|
+
end
|
157
|
+
|
158
|
+
# @return [Nokogiri::XML::Document] object
|
159
|
+
def doc
|
160
|
+
# remove returns and whitespaces between tags
|
161
|
+
xml_string = xml.gsub("\n", "").gsub(/>\s+</, "><")
|
162
|
+
# return Nokogiri::XML::Document
|
163
|
+
@doc ||= Nokogiri.XML(xml_string)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return [Nokogiri::XML::Document] object, containing only the status section
|
167
|
+
# @note This is a fix for Nokogiri's trouble finding xpath elements after 'xlmns' attribute in a node.
|
168
|
+
def status_xml
|
169
|
+
@status_xml ||= Nokogiri.XML(doc.xpath("//S:Body").first.children.first.children.first.to_xml)
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|