diff --git a/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb new file mode 100644 index 00000000000..4055a9197bc --- /dev/null +++ b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb @@ -0,0 +1,32 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module CyberSourceCommon + def check_billing_field_value(default, submitted) + if submitted.nil? + nil + elsif submitted.blank? + default + else + submitted + end + end + + def address_names(address_name, payment_method) + names = split_names(address_name) + return names if names.any?(&:present?) + + [ + payment_method&.first_name, + payment_method&.last_name + ] + end + + def lookup_country_code(country_field) + return unless country_field.present? + + country_code = Country.find(country_field) + country_code&.code(:alpha2) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb new file mode 100644 index 00000000000..0969bff3f30 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb @@ -0,0 +1,218 @@ +require 'active_merchant/billing/gateways/cyber_source/cyber_source_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CyberSourceRestGateway < Gateway + include ActiveMerchant::Billing::CyberSourceCommon + + self.test_url = 'https://apitest.cybersource.com' + self.live_url = 'https://api.cybersource.com' + + self.supported_countries = ActiveMerchant::Billing::CyberSourceGateway.supported_countries + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada] + + self.homepage_url = 'http://www.cybersource.com' + self.display_name = 'Cybersource REST' + + CREDIT_CARD_CODES = { + american_express: '003', + cartes_bancaires: '036', + dankort: '034', + diners_club: '005', + discover: '004', + elo: '054', + jcb: '007', + maestro: '042', + master: '002', + unionpay: '062', + visa: '001' + } + + def initialize(options = {}) + requires!(options, :merchant_id, :public_key, :private_key) + super + end + + def purchase(money, payment, options = {}) + authorize(money, payment, options, true) + end + + def authorize(money, payment, options = {}, capture = false) + post = build_auth_request(money, payment, options) + post[:processingInformation] = { capture: true } if capture + + commit('/pts/v2/payments/', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]'). + gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]'). + gsub(/(signature=")[^"]*/, '\1[FILTERED]'). + gsub(/(keyid=")[^"]*/, '\1[FILTERED]'). + gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]') + end + + private + + def build_auth_request(amount, payment, options) + { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post| + add_customer_id(post, options) + add_code(post, options) + add_credit_card(post, payment) + add_amount(post, amount) + add_address(post, payment, options[:billing_address], options, :billTo) + add_address(post, payment, options[:shipping_address], options, :shipTo) + end.compact + end + + def add_code(post, options) + return unless options[:order_id].present? + + post[:clientReferenceInformation][:code] = options[:order_id] + end + + def add_customer_id(post, options) + return unless options[:customer_id].present? + + post[:paymentInformation][:customer] = { customerId: options[:customer_id] } + end + + def add_amount(post, amount) + currency = options[:currency] || currency(amount) + + post[:orderInformation][:amountDetails] = { + totalAmount: localized_amount(amount, currency), + currency: currency + } + end + + def add_credit_card(post, creditcard) + post[:paymentInformation][:card] = { + number: creditcard.number, + expirationMonth: format(creditcard.month, :two_digits), + expirationYear: format(creditcard.year, :four_digits), + securityCode: creditcard.verification_value, + type: CREDIT_CARD_CODES[card_brand(creditcard).to_sym] + } + end + + def add_address(post, payment_method, address, options, address_type) + return unless address.present? + + first_name, last_name = address_names(address[:name], payment_method) + + post[:orderInformation][address_type] = { + firstName: first_name, + lastName: last_name, + address1: address[:address1], + address2: address[:address2], + locality: address[:city], + administrativeArea: address[:state], + postalCode: address[:zip], + country: lookup_country_code(address[:country])&.value, + email: options[:email].presence || 'null@cybersource.com', + phoneNumber: address[:phone] + # merchantTaxID: ship_to ? options[:merchant_tax_id] : nil, + # company: address[:company], + # companyTaxID: address[:companyTaxID], + # ipAddress: options[:ip], + # driversLicenseNumber: options[:drivers_license_number], + # driversLicenseState: options[:drivers_license_state], + }.compact + end + + def url(action) + "#{(test? ? test_url : live_url)}#{action}" + end + + def host + URI.parse(url('')).host + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, post) + response = parse(ssl_post(url(action), post.to_json, auth_headers(action, post))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response.dig('processorInformation', 'avs', 'code')), + # cvv_result: CVVResult.new(response['some_cvv_response_key']), + test: test?, + error_code: error_code_from(response) + ) + rescue ActiveMerchant::ResponseError => e + response = e.response.body.present? ? parse(e.response.body) : { 'response' => { 'rmsg' => e.response.msg } } + Response.new(false, response.dig('response', 'rmsg'), response, test: test?) + end + + def success_from(response) + response['status'] == 'AUTHORIZED' + end + + def message_from(response) + return response['status'] if success_from(response) + + response['errorInformation']['message'] + end + + def authorization_from(response) + response['id'] + end + + def error_code_from(response) + response['errorInformation']['reason'] unless success_from(response) + end + + # This implementation follows the Cybersource guide on how create the request signature, see: + # https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/GenerateHeader/httpSignatureAuthentication.html + def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Time.now.httpdate) + string_to_sign = { + host: host, + date: gmtdatetime, + "(request-target)": "#{http_method} #{resource}", + digest: digest, + "v-c-merchant-id": @options[:merchant_id] + }.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8) + + { + keyid: @options[:public_key], + algorithm: 'HmacSHA256', + headers: "host date (request-target)#{digest.present? ? ' digest' : ''} v-c-merchant-id", + signature: sign_payload(string_to_sign) + }.map { |k, v| %{#{k}="#{v}"} }.join(', ') + end + + def sign_payload(payload) + decoded_key = Base64.decode64(@options[:private_key]) + Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', decoded_key, payload)) + end + + def auth_headers(action, post, http_method = 'post') + digest = "SHA-256=#{Digest::SHA256.base64digest(post.to_json)}" if post.present? + date = Time.now.httpdate + + { + 'Accept' => 'application/hal+json;charset=utf-8', + 'Content-Type' => 'application/json;charset=utf-8', + 'V-C-Merchant-Id' => @options[:merchant_id], + 'Date' => date, + 'Host' => host, + 'Signature' => get_http_signature(action, digest, http_method, date), + 'Digest' => digest + } + end + end + end +end diff --git a/test/fixtures.yml b/test/fixtures.yml index 66606cf5e6d..82237b04bf9 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -272,6 +272,12 @@ cyber_source_latam_pe: login: merchant_id password: soap_key +# Working credentials, no need to replace +cybersource_rest: + merchant_id: "testrest" + public_key: "08c94330-f618-42a3-b09d-e1e43be5efda" + private_key: "yBJxy6LjM2TmcPGu+GaJrHtkke25fPpUX+UY6/L/1tE=" + # Working credentials, no need to replace d_local: login: aeaf9bbfa1 diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb new file mode 100644 index 00000000000..06b688a3a2e --- /dev/null +++ b/test/remote/gateways/remote_cyber_source_rest_test.rb @@ -0,0 +1,82 @@ +require 'test_helper' + +class RemoteCyberSourceRestTest < Test::Unit::TestCase + def setup + @gateway = CyberSourceRestGateway.new(fixtures(:cybersource_rest)) + @amount = 10221 + @card_without_funds = credit_card('42423482938483873') + @visa_card = credit_card('4111111111111111', + verification_value: '987', + month: 12, + year: 2031) + + @billing_address = { + name: 'John Doe', + address1: '1 Market St', + city: 'san francisco', + state: 'CA', + zip: '94105', + country: 'US', + phone: '4158880000' + } + + @options = { + order_id: generate_unique_id, + currency: 'USD', + email: 'test@cybs.com' + } + end + + def test_handle_credentials_error + gateway = CyberSourceRestGateway.new({ merchant_id: 'abc123', public_key: 'abc456', private_key: 'def789' }) + response = gateway.authorize(@amount, @visa_card, @options) + + assert_equal('Authentication Failed', response.message) + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @visa_card, @options) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + refute_empty response.params['_links']['capture'] + end + + def test_successful_authorize_with_billing_address + @options[:billing_address] = @billing_address + response = @gateway.authorize(@amount, @visa_card, @options) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + refute_empty response.params['_links']['capture'] + end + + def test_failure_authorize_with_declined_credit_card + response = @gateway.authorize(@amount, @card_without_funds, @options) + + assert_failure response + assert_match %r{Invalid account}, response.message + assert_equal 'INVALID_ACCOUNT', response.error_code + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @visa_card, @options) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + assert_nil response.params['_links']['capture'] + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.authorize(@amount, @visa_card, @options) + end + + transcript = @gateway.scrub(transcript) + assert_scrubbed(@visa_card.number, transcript) + assert_scrubbed(@visa_card.verification_value, transcript) + end +end diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb new file mode 100644 index 00000000000..70258beb9d2 --- /dev/null +++ b/test/unit/gateways/cyber_source_rest_test.rb @@ -0,0 +1,262 @@ +require 'test_helper' + +class CyberSourceRestTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CyberSourceRestGateway.new( + merchant_id: 'abc123', + public_key: 'def345', + private_key: "NYlM1sgultLjvgaraWvDCXykdz1buqOW8yXE3pMlmxQ=\n" + ) + @credit_card = credit_card('4111111111111111', + verification_value: '987', + month: 12, + year: 2031) + @amount = 100 + @options = { + order_id: '1', + description: 'Store Purchase', + billing_address: { + name: 'John Doe', + address1: '1 Market St', + city: 'san francisco', + state: 'CA', + zip: '94105', + country: 'US', + phone: '4158880000' + }, + email: 'test@cybs.com' + } + + @gmt_time = Time.now.httpdate + @digest = 'SHA-256=gXWufV4Zc7VkN9Wkv9jh/JuAVclqDusx3vkyo3uJFWU=' + @resource = '/pts/v2/payments/' + end + + def test_required_merchant_id_and_secret + error = assert_raises(ArgumentError) { CyberSourceRestGateway.new } + assert_equal 'Missing required parameter: merchant_id', error.message + end + + def test_supported_card_types + assert_equal CyberSourceRestGateway.supported_cardtypes, %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada] + end + + def test_properly_format_on_zero_decilmal + stub_comms do + @gateway.authorize(1000, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + card = request['paymentInformation']['card'] + amount_details = request['orderInformation']['amountDetails'] + + assert_equal '1', request['clientReferenceInformation']['code'] + assert_equal '2031', card['expirationYear'] + assert_equal '12', card['expirationMonth'] + assert_equal '987', card['securityCode'] + assert_equal '001', card['type'] + assert_equal 'USD', amount_details['currency'] + assert_equal '10.00', amount_details['totalAmount'] + end.respond_with(successful_purchase_response) + end + + def test_should_create_an_http_signature_for_a_post + signature = @gateway.send :get_http_signature, @resource, @digest, 'post', @gmt_time + + parsed = parse_signature(signature) + + assert_equal 'def345', parsed['keyid'] + assert_equal 'HmacSHA256', parsed['algorithm'] + assert_equal 'host date (request-target) digest v-c-merchant-id', parsed['headers'] + assert_equal %w[algorithm headers keyid signature], signature.split(', ').map { |v| v.split('=').first }.sort + end + + def test_should_create_an_http_signature_for_a_get + signature = @gateway.send :get_http_signature, @resource, nil, 'get', @gmt_time + + parsed = parse_signature(signature) + assert_equal 'host date (request-target) v-c-merchant-id', parsed['headers'] + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_including_customer_if_customer_id_present + post = { paymentInformation: {} } + + @gateway.send :add_customer_id, post, {} + assert_nil post[:paymentInformation][:customer] + + @gateway.send :add_customer_id, post, { customer_id: 10 } + assert_equal 10, post[:paymentInformation][:customer][:customerId] + end + + def test_add_ammount_and_currency + post = { orderInformation: {} } + + @gateway.send :add_amount, post, 10221 + + assert_equal '102.21', post.dig(:orderInformation, :amountDetails, :totalAmount) + assert_equal 'USD', post.dig(:orderInformation, :amountDetails, :currency) + end + + def test_add_credit_card_data + post = { paymentInformation: {} } + @gateway.send :add_credit_card, post, @credit_card + + card = post[:paymentInformation][:card] + assert_equal @credit_card.number, card[:number] + assert_equal '2031', card[:expirationYear] + assert_equal '12', card[:expirationMonth] + assert_equal '987', card[:securityCode] + assert_equal '001', card[:type] + end + + def test_add_billing_address + post = { orderInformation: {} } + + @gateway.send :add_address, post, @credit_card, @options[:billing_address], @options, :billTo + + address = post[:orderInformation][:billTo] + + assert_equal 'John', address[:firstName] + assert_equal 'Doe', address[:lastName] + assert_equal '1 Market St', address[:address1] + assert_equal 'san francisco', address[:locality] + assert_equal 'US', address[:country] + assert_equal 'test@cybs.com', address[:email] + assert_equal '4158880000', address[:phoneNumber] + end + + def test_add_shipping_address + post = { orderInformation: {} } + @options[:shipping_address] = @options.delete(:billing_address) + + @gateway.send :add_address, post, @credit_card, @options[:shipping_address], @options, :shipTo + + address = post[:orderInformation][:shipTo] + + assert_equal 'John', address[:firstName] + assert_equal 'Doe', address[:lastName] + assert_equal '1 Market St', address[:address1] + assert_equal 'san francisco', address[:locality] + assert_equal 'US', address[:country] + assert_equal 'test@cybs.com', address[:email] + assert_equal '4158880000', address[:phoneNumber] + end + + def test_url_building + assert_equal "#{@gateway.class.test_url}/action", @gateway.send(:url, '/action') + end + + private + + def parse_signature(signature) + signature.gsub(/=\"$/, '').delete('"').split(', ').map { |x| x.split('=') }.to_h + end + + def pre_scrubbed + <<-PRE + <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"08c94330-f618-42a3-b09d-e1e43be5efda\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"DJHeHWceVrsJydd8BCbGowr9dzQ/ry5cGN1FocLakEw=\"\r\nDigest: SHA-256=wuV1cxGzs6KpuUKJmlD7pKV6MZ/5G1wQVoYbf8cRChM=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n" + <- "{\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"paymentInformation\":{\"card\":{\"number\":\"4111111111111111\",\"expirationMonth\":\"12\",\"expirationYear\":\"2031\",\"securityCode\":\"987\",\"type\":\"001\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"},\"shipTo\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"email\":\"test@cybs.com\"}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Cache-Control: no-cache, no-store, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Expires: -1\r\n" + -> "Strict-Transport-Security: max-age=31536000\r\n" + -> "Content-Type: application/hal+json\r\n" + -> "Content-Length: 905\r\n" + -> "x-response-time: 291ms\r\n" + -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n" + -> "Connection: close\r\n" + -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n" + -> "\r\n" + reading 905 bytes... + -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/6750124114786780104953\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/captures\"}},\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"id\":\"6750124114786780104953\",\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"type\":\"001\"},\"card\":{\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"111111\"},\"processorInformation\":{\"approvalCode\":\"888888\",\"networkTransactionId\":\"123456789619999\",\"transactionId\":\"123456789619999\",\"responseCode\":\"100\",\"avs\":{\"code\":\"X\",\"codeRaw\":\"I1\"}},\"reconciliationId\":\"78243988SD9YL291\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2023-01-29T17:13:31Z\"}" + PRE + end + + def post_scrubbed + <<-POST + <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"[FILTERED]\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"[FILTERED]\"\r\nDigest: SHA-256=[FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n" + <- "{\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"paymentInformation\":{\"card\":{\"number\":\"[FILTERED]\",\"expirationMonth\":\"12\",\"expirationYear\":\"2031\",\"securityCode\":\"[FILTERED]\",\"type\":\"001\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"},\"shipTo\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"email\":\"test@cybs.com\"}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Cache-Control: no-cache, no-store, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Expires: -1\r\n" + -> "Strict-Transport-Security: max-age=31536000\r\n" + -> "Content-Type: application/hal+json\r\n" + -> "Content-Length: 905\r\n" + -> "x-response-time: 291ms\r\n" + -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n" + -> "Connection: close\r\n" + -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n" + -> "\r\n" + reading 905 bytes... + -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/6750124114786780104953\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/captures\"}},\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"id\":\"6750124114786780104953\",\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"type\":\"001\"},\"card\":{\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"111111\"},\"processorInformation\":{\"approvalCode\":\"888888\",\"networkTransactionId\":\"123456789619999\",\"transactionId\":\"123456789619999\",\"responseCode\":\"100\",\"avs\":{\"code\":\"X\",\"codeRaw\":\"I1\"}},\"reconciliationId\":\"78243988SD9YL291\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2023-01-29T17:13:31Z\"}" + POST + end + + def successful_purchase_response + <<-RESPONSE + { + "_links": { + "authReversal": { + "method": "POST", + "href": "/pts/v2/payments/6750124114786780104953/reversals" + }, + "self": { + "method": "GET", + "href": "/pts/v2/payments/6750124114786780104953" + }, + "capture": { + "method": "POST", + "href": "/pts/v2/payments/6750124114786780104953/captures" + } + }, + "clientReferenceInformation": { + "code": "b8779865d140125036016a0f85db907f" + }, + "id": "6750124114786780104953", + "orderInformation": { + "amountDetails": { + "authorizedAmount": "102.21", + "currency": "USD" + } + }, + "paymentAccountInformation": { + "card": { + "type": "001" + } + }, + "paymentInformation": { + "tokenizedCard": { + "type": "001" + }, + "card": { + "type": "001" + } + }, + "pointOfSaleInformation": { + "terminalId": "111111" + }, + "processorInformation": { + "approvalCode": "888888", + "networkTransactiDDDonId": "123456789619999", + "transactionId": "123456789619999", + "responseCode": "100", + "avs": { + "code": "X", + "codeRaw": "I1" + } + }, + "reconciliationId": "78243988SD9YL291", + "status": "AUTHORIZED", + "submitTimeUtc": "2023-01-29T17:13:31Z" + } + RESPONSE + end +end