docdata 0.0.1 → 0.0.2
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 +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
|