lib/active_merchant/billing/gateways/cyber_source.rb in activemerchant-1.133.0 vs lib/active_merchant/billing/gateways/cyber_source.rb in activemerchant-1.137.0
- old
+ new
@@ -31,10 +31,19 @@
american_express: 'aesk',
jcb: 'js',
discover: 'pb',
diners_club: 'pb'
}.freeze
+ THREEDS_EXEMPTIONS = {
+ authentication_outage: 'authenticationOutageExemptionIndicator',
+ corporate_card: 'secureCorporatePaymentIndicator',
+ delegated_authentication: 'delegatedAuthenticationExemptionIndicator',
+ low_risk: 'riskAnalysisExemptionIndicator',
+ low_value: 'lowValueExemptionIndicator',
+ stored_credential: 'stored_credential',
+ trusted_merchant: 'trustedMerchantExemptionIndicator'
+ }
DEFAULT_COLLECTION_INDICATOR = 2
self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro elo]
self.supported_countries = %w(US AE BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK)
@@ -51,11 +60,12 @@
discover: '004',
diners_club: '005',
jcb: '007',
dankort: '034',
maestro: '042',
- elo: '054'
+ elo: '054',
+ carnet: '058'
}
@@decision_codes = {
accept: 'ACCEPT',
review: 'REVIEW'
@@ -130,15 +140,20 @@
r701: 'Export bill_country/ship_country match',
r702: 'Export email_country match',
r703: 'Export hostname_country/ip_country match'
}
- @@payment_solution = {
+ @@wallet_payment_solution = {
apple_pay: '001',
google_pay: '012'
}
+ NT_PAYMENT_SOLUTION = {
+ 'master' => '014',
+ 'visa' => '015'
+ }
+
# These are the options that can be used when creating a new CyberSource
# Gateway object.
#
# :login => your username
#
@@ -159,23 +174,35 @@
def initialize(options = {})
requires!(options, :login, :password)
super
end
- def authorize(money, creditcard_or_reference, options = {})
- setup_address_hash(options)
- commit(build_auth_request(money, creditcard_or_reference, options), :authorize, money, options)
+ def authorize(money, payment_method, options = {})
+ if valid_payment_method?(payment_method)
+ setup_address_hash(options)
+ commit(build_auth_request(money, payment_method, options), :authorize, money, options)
+ else
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
+ end
end
def capture(money, authorization, options = {})
setup_address_hash(options)
commit(build_capture_request(money, authorization, options), :capture, money, options)
end
- def purchase(money, payment_method_or_reference, options = {})
- setup_address_hash(options)
- commit(build_purchase_request(money, payment_method_or_reference, options), :purchase, money, options)
+ def purchase(money, payment_method, options = {})
+ if valid_payment_method?(payment_method)
+ setup_address_hash(options)
+ commit(build_purchase_request(money, payment_method, options), :purchase, money, options)
+ else
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
+ end
end
def void(identification, options = {})
commit(build_void_request(identification, options), :void, nil, options)
end
@@ -204,12 +231,18 @@
# Stores a customer subscription/profile with type "on-demand".
# To charge the card while creating a profile, pass
# options[:setup_fee] => money
def store(payment_method, options = {})
- setup_address_hash(options)
- commit(build_create_subscription_request(payment_method, options), :store, nil, options)
+ if valid_payment_method?(payment_method)
+ setup_address_hash(options)
+ commit(build_create_subscription_request(payment_method, options), :store, nil, options)
+ else
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
+ end
end
# Updates a customer subscription/profile
def update(reference, creditcard, options = {})
requires!(options, :order_id)
@@ -270,10 +303,12 @@
gsub(%r((<wsse:Password [^>]*>)[^<]*(</wsse:Password>))i, '\1[FILTERED]\2').
gsub(%r((<accountNumber>)[^<]*(</accountNumber>))i, '\1[FILTERED]\2').
gsub(%r((<cvNumber>)[^<]*(</cvNumber>))i, '\1[FILTERED]\2').
gsub(%r((<cavv>)[^<]*(</cavv>))i, '\1[FILTERED]\2').
gsub(%r((<xid>)[^<]*(</xid>))i, '\1[FILTERED]\2').
+ gsub(%r((<networkTokenCryptogram>)[^<]*(</networkTokenCryptogram>))i, '\1[FILTERED]\2').
+ gsub(%r((<requestorID>)[^<]*(</requestorID>))i, '\1[FILTERED]\2').
gsub(%r((<authenticationData>)[^<]*(</authenticationData>))i, '\1[FILTERED]\2')
end
def supports_network_tokenization?
true
@@ -284,10 +319,16 @@
response.params['reasonCode'] == '102'
end
private
+ def valid_payment_method?(payment_method)
+ return true unless payment_method.is_a?(NetworkTokenizationCreditCard)
+
+ %w(visa master american_express).include?(card_brand(payment_method))
+ end
+
# Create all required address hash key value pairs
# If a value of nil is received, that value will be passed on to the gateway and will not be replaced with a default value
# Billing address fields received without an override value or with an empty string value will be replaced with the default_address values
def setup_address_hash(options)
default_address = {
@@ -315,21 +356,23 @@
def build_auth_request(money, creditcard_or_reference, options)
xml = Builder::XmlMarkup.new indent: 2
add_customer_id(xml, options)
add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
- add_other_tax(xml, options)
add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
+ add_mastercard_network_tokenization_ucaf_data(xml, creditcard_or_reference, options)
add_decision_manager_fields(xml, options)
+ add_other_tax(xml, options)
add_mdd_fields(xml, options)
add_auth_service(xml, creditcard_or_reference, options)
+ add_capture_service_fields_with_run_false(xml, options)
add_threeds_services(xml, options)
add_business_rules_data(xml, creditcard_or_reference, options)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
- add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
- add_payment_solution(xml, creditcard_or_reference.source) if network_tokenization?(creditcard_or_reference)
+ add_payment_network_token(xml, creditcard_or_reference, options)
+ add_payment_solution(xml, creditcard_or_reference)
add_tax_management_indicator(xml, options)
add_stored_credential_subsequent_auth(xml, options)
add_issuer_additional_data(xml, options)
add_partner_solution_id(xml)
add_stored_credential_options(xml, options)
@@ -378,13 +421,14 @@
def build_purchase_request(money, payment_method_or_reference, options)
xml = Builder::XmlMarkup.new indent: 2
add_customer_id(xml, options)
add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
- add_other_tax(xml, options)
add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
+ add_mastercard_network_tokenization_ucaf_data(xml, payment_method_or_reference, options)
add_decision_manager_fields(xml, options)
+ add_other_tax(xml, options)
add_mdd_fields(xml, options)
if (!payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check') || reference_is_a_check?(payment_method_or_reference)
add_check_service(xml)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
@@ -396,12 +440,12 @@
add_purchase_service(xml, payment_method_or_reference, options)
add_threeds_services(xml, options)
add_business_rules_data(xml, payment_method_or_reference, options)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
- add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
- add_payment_solution(xml, payment_method_or_reference.source) if network_tokenization?(payment_method_or_reference)
+ add_payment_network_token(xml, payment_method_or_reference, options)
+ add_payment_solution(xml, payment_method_or_reference)
add_tax_management_indicator(xml, options)
add_stored_credential_subsequent_auth(xml, options)
add_issuer_additional_data(xml, options)
add_partner_solution_id(xml)
add_stored_credential_options(xml, options)
@@ -485,11 +529,11 @@
if options[:setup_fee]
if card_brand(payment_method) == 'check'
add_check_service(xml)
else
add_purchase_service(xml, payment_method, options)
- add_payment_network_token(xml) if network_tokenization?(payment_method)
+ add_payment_network_token(xml, payment_method, options)
end
end
add_subscription_create_service(xml, options)
add_business_rules_data(xml, payment_method, options)
add_tax_management_indicator(xml, options)
@@ -554,26 +598,38 @@
end
end
end
def add_merchant_data(xml, options)
- xml.tag! 'merchantID', @options[:login]
+ xml.tag! 'merchantID', options[:merchant_id] || @options[:login]
xml.tag! 'merchantReferenceCode', options[:order_id] || generate_unique_id
xml.tag! 'clientLibrary', 'Ruby Active Merchant'
xml.tag! 'clientLibraryVersion', VERSION
xml.tag! 'clientEnvironment', RUBY_PLATFORM
add_merchant_descriptor(xml, options)
end
def add_merchant_descriptor(xml, options)
- return unless options[:merchant_descriptor] || options[:user_po] || options[:taxable] || options[:reference_data_code] || options[:invoice_number]
+ return unless options[:merchant_descriptor] ||
+ options[:user_po] ||
+ options[:taxable] ||
+ options[:reference_data_code] ||
+ options[:invoice_number] ||
+ options[:merchant_descriptor_city] ||
+ options[:submerchant_id] ||
+ options[:merchant_descriptor_state] ||
+ options[:merchant_descriptor_country]
xml.tag! 'invoiceHeader' do
xml.tag! 'merchantDescriptor', options[:merchant_descriptor] if options[:merchant_descriptor]
+ xml.tag! 'merchantDescriptorCity', options[:merchant_descriptor_city] if options[:merchant_descriptor_city]
+ xml.tag! 'merchantDescriptorState', options[:merchant_descriptor_state] if options[:merchant_descriptor_state]
+ xml.tag! 'merchantDescriptorCountry', options[:merchant_descriptor_country] if options[:merchant_descriptor_country]
xml.tag! 'userPO', options[:user_po] if options[:user_po]
xml.tag! 'taxable', options[:taxable] if options[:taxable]
+ xml.tag! 'submerchantID', options[:submerchant_id] if options[:submerchant_id]
xml.tag! 'referenceDataCode', options[:reference_data_code] if options[:reference_data_code]
xml.tag! 'invoiceNumber', options[:invoice_number] if options[:invoice_number]
end
end
@@ -675,14 +731,20 @@
xml.tag! 'enabled', options[:decision_manager_enabled] if options[:decision_manager_enabled]
xml.tag! 'profile', options[:decision_manager_profile] if options[:decision_manager_profile]
end
end
- def add_payment_solution(xml, source)
- return unless (payment_solution = @@payment_solution[source])
+ def add_payment_solution(xml, payment_method)
+ return unless network_tokenization?(payment_method)
- xml.tag! 'paymentSolution', payment_solution
+ case payment_method.source
+ when :network_token
+ payment_solution = NT_PAYMENT_SOLUTION[payment_method.brand]
+ xml.tag! 'paymentSolution', payment_solution if payment_solution
+ when :apple_pay, :google_pay
+ xml.tag! 'paymentSolution', @@wallet_payment_solution[payment_method.source]
+ end
end
def add_issuer_additional_data(xml, options)
return unless options[:issuer_additional_data]
@@ -690,11 +752,11 @@
xml.tag! 'additionalData', options[:issuer_additional_data]
end
end
def add_other_tax(xml, options)
- return unless options[:local_tax_amount] || options[:national_tax_amount] || options[:national_tax_indicator]
+ return unless %i[vat_tax_rate local_tax_amount national_tax_amount national_tax_indicator].any? { |gsf| options.include?(gsf) }
xml.tag! 'otherTax' do
xml.tag! 'vatTaxRate', options[:vat_tax_rate] if options[:vat_tax_rate]
xml.tag! 'localTaxAmount', options[:local_tax_amount] if options[:local_tax_amount]
xml.tag! 'nationalTaxAmount', options[:national_tax_amount] if options[:national_tax_amount]
@@ -729,25 +791,43 @@
end
end
def add_auth_service(xml, payment_method, options)
if network_tokenization?(payment_method)
- add_auth_network_tokenization(xml, payment_method, options)
+ if payment_method.source == :network_token
+ add_auth_network_tokenization(xml, payment_method, options)
+ else
+ add_auth_wallet(xml, payment_method, options)
+ end
else
xml.tag! 'ccAuthService', { 'run' => 'true' } do
if options[:three_d_secure]
add_normalized_threeds_2_data(xml, payment_method, options)
+ add_threeds_exemption_data(xml, options) if options[:three_ds_exemption_type]
else
indicator = options[:commerce_indicator] || stored_credential_commerce_indicator(options)
xml.tag!('commerceIndicator', indicator) if indicator
end
+ xml.tag!('aggregatorID', options[:aggregator_id]) if options[:aggregator_id]
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
+ xml.tag!('firstRecurringPayment', options[:first_recurring_payment]) if options[:first_recurring_payment]
xml.tag!('mobileRemotePaymentType', options[:mobile_remote_payment_type]) if options[:mobile_remote_payment_type]
end
end
end
+ def add_threeds_exemption_data(xml, options)
+ return unless options[:three_ds_exemption_type]
+
+ exemption = options[:three_ds_exemption_type].to_sym
+
+ case exemption
+ when :authentication_outage, :corporate_card, :delegated_authentication, :low_risk, :low_value, :trusted_merchant
+ xml.tag!(THREEDS_EXEMPTIONS[exemption], '1')
+ end
+ end
+
def add_incremental_auth_service(xml, authorization, options)
xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
xml.tag! 'authRequestID', authorization
end
xml.tag! 'subsequentAuthReason', options[:auth_reason]
@@ -791,62 +871,90 @@
xml.tag!('collectionIndicator', options[:collection_indicator] || DEFAULT_COLLECTION_INDICATOR)
end
end
def stored_credential_commerce_indicator(options)
- return unless options[:stored_credential]
+ return unless (reason_type = options.dig(:stored_credential, :reason_type))
- return if options[:stored_credential][:initial_transaction]
-
- case options[:stored_credential][:reason_type]
- when 'installment' then 'install'
- when 'recurring' then 'recurring'
+ case reason_type
+ when 'installment'
+ 'install'
+ when 'recurring'
+ 'recurring'
+ else
+ 'internet'
end
end
def network_tokenization?(payment_method)
payment_method.is_a?(NetworkTokenizationCreditCard)
end
+ def subsequent_nt_apple_pay_auth(source, options)
+ return unless options[:stored_credential] || options[:stored_credential_overrides]
+ return unless @@wallet_payment_solution[source]
+
+ options.dig(:stored_credential_overrides, :subsequent_auth) || options.dig(:stored_credential, :initiator) == 'merchant'
+ end
+
def add_auth_network_tokenization(xml, payment_method, options)
- return unless network_tokenization?(payment_method)
+ commerce_indicator = stored_credential_commerce_indicator(options) || 'internet'
+ xml.tag! 'ccAuthService', { 'run' => 'true' } do
+ xml.tag!('networkTokenCryptogram', payment_method.payment_cryptogram)
+ xml.tag!('commerceIndicator', commerce_indicator)
+ xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
+ end
+ end
+ def add_auth_wallet(xml, payment_method, options)
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
+
brand = card_brand(payment_method).to_sym
case brand
when :visa
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('cavv', payment_method.payment_cryptogram)
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
- xml.tag!('xid', payment_method.payment_cryptogram)
+ xml.tag!('cavv', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
+ xml.tag!('xid', payment_method.payment_cryptogram) unless commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :master
- xml.tag! 'ucaf' do
- xml.tag!('authenticationData', payment_method.payment_cryptogram)
- xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
- end
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :american_express
cryptogram = Base64.decode64(payment_method.payment_cryptogram)
xml.tag! 'ccAuthService', { 'run' => 'true' } do
xml.tag!('cavv', Base64.encode64(cryptogram[0...20]))
xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
xml.tag!('xid', Base64.encode64(cryptogram[20...40])) if cryptogram.bytes.count > 20
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
- else
- raise ArgumentError.new("Payment method #{brand} is not supported, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
end
end
- def add_payment_network_token(xml)
+ def add_mastercard_network_tokenization_ucaf_data(xml, payment_method, options)
+ return unless network_tokenization?(payment_method) && card_brand(payment_method).to_sym == :master
+ return if payment_method.source == :network_token
+
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
+
+ xml.tag! 'ucaf' do
+ xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
+ end
+ end
+
+ def add_payment_network_token(xml, payment_method, options)
+ return unless network_tokenization?(payment_method)
+
+ transaction_type = payment_method.source == :network_token ? '3' : '1'
xml.tag! 'paymentNetworkToken' do
- xml.tag!('transactionType', '1')
+ xml.tag!('requestorID', options[:trid]) if transaction_type == '3' && options[:trid]
+ xml.tag!('transactionType', transaction_type)
end
end
def add_capture_service(xml, request_id, request_token, options)
xml.tag! 'ccCaptureService', { 'run' => 'true' } do
@@ -855,14 +963,23 @@
xml.tag! 'gratuityAmount', options[:gratuity_amount] if options[:gratuity_amount]
xml.tag! 'reconciliationID', options[:reconciliation_id] if options[:reconciliation_id]
end
end
+ def add_capture_service_fields_with_run_false(xml, options)
+ return unless options[:gratuity_amount]
+
+ xml.tag! 'ccCaptureService', { 'run' => 'false' } do
+ xml.tag! 'gratuityAmount', options[:gratuity_amount]
+ end
+ end
+
def add_purchase_service(xml, payment_method, options)
add_auth_service(xml, payment_method, options)
xml.tag! 'ccCaptureService', { 'run' => 'true' } do
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
+ xml.tag!('gratuityAmount', options[:gratuity_amount]) if options[:gratuity_amount]
end
end
def add_void_service(xml, request_id, request_token)
xml.tag! 'voidService', { 'run' => 'true' } do
@@ -1001,23 +1118,39 @@
end
def add_stored_credential_options(xml, options = {})
return unless options[:stored_credential] || options[:stored_credential_overrides]
- stored_credential_subsequent_auth_first = 'true' if options.dig(:stored_credential, :initial_transaction)
+ stored_credential_subsequent_auth_first = 'true' if cardholder_or_initiated_transaction?(options)
stored_credential_transaction_id = options.dig(:stored_credential, :network_transaction_id) if options.dig(:stored_credential, :initiator) == 'merchant'
- stored_credential_subsequent_auth_stored_cred = 'true' if options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction) || options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
+ stored_credential_subsequent_auth_stored_cred = 'true' if subsequent_cardholder_initiated_transaction?(options) || unscheduled_merchant_initiated_transaction?(options) || threeds_stored_credential_exemption?(options)
override_subsequent_auth_first = options.dig(:stored_credential_overrides, :subsequent_auth_first)
override_subsequent_auth_transaction_id = options.dig(:stored_credential_overrides, :subsequent_auth_transaction_id)
override_subsequent_auth_stored_cred = options.dig(:stored_credential_overrides, :subsequent_auth_stored_credential)
xml.subsequentAuthFirst override_subsequent_auth_first.nil? ? stored_credential_subsequent_auth_first : override_subsequent_auth_first
xml.subsequentAuthTransactionID override_subsequent_auth_transaction_id.nil? ? stored_credential_transaction_id : override_subsequent_auth_transaction_id
xml.subsequentAuthStoredCredential override_subsequent_auth_stored_cred.nil? ? stored_credential_subsequent_auth_stored_cred : override_subsequent_auth_stored_cred
end
+ def cardholder_or_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'cardholder' || options.dig(:stored_credential, :initial_transaction)
+ end
+
+ def subsequent_cardholder_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction)
+ end
+
+ def unscheduled_merchant_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
+ end
+
+ def threeds_stored_credential_exemption?(options)
+ options[:three_ds_exemption_type] == THREEDS_EXEMPTIONS[:stored_credential]
+ end
+
def add_partner_solution_id(xml)
return unless application_id
xml.tag!('partnerSolutionID', application_id)
end
@@ -1066,16 +1199,20 @@
message = message_from(response)
authorization = success || in_fraud_review?(response) ? authorization_from(response, action, amount, options) : nil
message = auto_void?(authorization_from(response, action, amount, options), response, message, options)
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
fraud_review: in_fraud_review?(response),
avs_result: { code: response[:avsCode] },
- cvv_result: response[:cvCode])
+ cvv_result: response[:cvCode]
+ )
end
def auto_void?(authorization, response, message, options = {})
return message unless response[:reasonCode] == '230' && options[:auto_void_230]
@@ -1113,9 +1250,10 @@
if /item/.match?(node.parent.name)
parent = node.parent.name
parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
parent += '_'
end
+ reply[:reconciliationID2] = node.text if node.name == 'reconciliationID' && reply[:reconciliationID]
reply["#{parent}#{node.name}".to_sym] ||= node.text
end
return reply
end