From 422e30ee8eef4e74d3db60566779d0b88fb0c2ba Mon Sep 17 00:00:00 2001 From: Rahul Sambari Date: Mon, 16 Dec 2019 15:52:16 -0800 Subject: [PATCH] Enable Tsys multipass Payment Gateway (#1) * Adding Tsys Multipass to ActiveMerchant * enabling verify * Adding Tsys Multipass to ActiveMerchant * enabling verify * unit specs * adding specs * fixing * fixing fixtures order spec * fixing rubocop issues --- .../billing/gateways/tsys_multipass.rb | 190 +++++++++++ lib/active_merchant/billing/response.rb | 3 +- test/fixtures.yml | 7 + .../gateways/remote_tsys_multipass_test.rb | 209 ++++++++++++ test/unit/gateways/tsys_multipass_test.rb | 312 ++++++++++++++++++ 5 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 lib/active_merchant/billing/gateways/tsys_multipass.rb create mode 100644 test/remote/gateways/remote_tsys_multipass_test.rb create mode 100644 test/unit/gateways/tsys_multipass_test.rb diff --git a/lib/active_merchant/billing/gateways/tsys_multipass.rb b/lib/active_merchant/billing/gateways/tsys_multipass.rb new file mode 100644 index 00000000000..08aa28ec22f --- /dev/null +++ b/lib/active_merchant/billing/gateways/tsys_multipass.rb @@ -0,0 +1,190 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TsysMultipassGateway < Gateway + self.display_name = 'TSYS Multipass' + self.homepage_url = 'https://www.tsys.com' + self.test_url = 'https://stagegw.transnox.com/servlets/TransNox_API_Server' + self.live_url = 'https://gateway.transit-pass.com/servlets/TransNox_API_Server/' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + + EMPTY_OBJ = {} + BLANK = '' + CONTENT_TYPE = 'application/json' + + WHITELISTED_RESPONSE_ROOT_KEYS = %w( + SaleResponse + AuthResponse + CaptureResponse + VoidResponse + ReturnResponse + ) + + attr_reader :response, :parsed_body + + def initialize(options={}) + requires!(options, :device_id, :transaction_key) + super + end + + def purchase(money, credit_card, options = {}) + commit( + request_body: { "Sale": request_params(options) }.to_json + ) + end + + def authorize(money, credit_card, options = {}) + commit( + request_body: { "Auth": request_params(options) }.to_json + ) + end + + def capture(money, tx_reference, options = {}) + commit( + request_body: { "Capture": request_params(options) }.to_json + ) + end + + def void(tx_reference, options = {}) + commit( + request_body: { "Void": request_params(options) }.to_json + ) + end + + def refund(money, tx_reference, options = {}) + commit( + request_body: { "Return": request_params(options) }.to_json + ) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/\"deviceID\":\K(?:(?!,).)*/, '[FILTERED]'). + gsub(/\"transactionKey\":\K(?:(?!,).)*/, '[FILTERED]'). + gsub(/\"cardNumber\":\K(?:(?!,).)*/, '[FILTERED]'). + gsub(/\"expirationDate\":\K(?:(?!,).)*/, '[FILTERED]'). + gsub(/\"token\":\K(?:(?!,).)*/, '[FILTERED]') + end + + private + + def request_params(options) + { + "deviceID": @options[:device_id], + "transactionKey": @options[:transaction_key] + }.merge!(options) + end + + def commit(request_body:) + @response = + Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |https| + request = Net::HTTP::Post.new(uri, {'Content-Type' => CONTENT_TYPE }) + request.body = request_body + # Making the call + https.request(request) + end + + # Parsing the response body + @parsed_body = parse(response.body) + + Response.new( + success?, + message, + parsed_body, + amount: amount, + error_code: error_code, + authorization: authorization, + test: test? + ) + end + + def success? + return false unless recognized_response_root_key? + + parsed_body_root_value['status'] == 'PASS' + end + + def message + return BLANK unless recognized_response_root_key? + + parsed_body_root_value['responseMessage'] + end + + def error_code + return BLANK unless error? + + response_code + end + + def error? + !response_code.start_with?('A') + end + + def response_code + return BLANK unless recognized_response_root_key? + + parsed_body_root_value['responseCode'] + end + + def authorization + return BLANK unless recognized_response_root_key? + + parsed_body_root_value['transactionID'] + end + + def amount + return BLANK unless recognized_response_root_key? + + parsed_body_root_value[amount_key_mapping[parsed_body_root_key]] + end + + # This method gives us mapping of which amount field to + # fetch based on the transaction response types. + def amount_key_mapping + { + 'SaleResponse' => 'processedAmount', + 'AuthResponse' => 'processedAmount', + 'CaptureResponse' => 'transactionAmount', + 'VoidResponse' => 'voidedAmount', + 'ReturnResponse' => 'returnedAmount' + } + end + + def recognized_response_root_key? + @recognized_response_root_key ||= + WHITELISTED_RESPONSE_ROOT_KEYS.include?(parsed_body_root_key) + end + + def parsed_body_root_key + parsed_body.first&.first + end + + def parsed_body_root_value + (parsed_body.present? && parsed_body.first[1]) || EMPTY_OBJ + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + EMPTY_OBJ + end + + def url + test? ? test_url : live_url + end + + def uri + @uri ||= URI(url) + end + end + end +end diff --git a/lib/active_merchant/billing/response.rb b/lib/active_merchant/billing/response.rb index 754f8f5d4a0..7cb0c4ad389 100644 --- a/lib/active_merchant/billing/response.rb +++ b/lib/active_merchant/billing/response.rb @@ -4,7 +4,7 @@ class Error < ActiveMerchantError #:nodoc: end class Response - attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization, :network_transaction_id + attr_reader :params, :message, :test, :amount, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization, :network_transaction_id def success? @success @@ -25,6 +25,7 @@ def fraud_review? def initialize(success, message, params = {}, options = {}) @success, @message, @params = success, message, params.stringify_keys @test = options[:test] || false + @amount = options[:amount] @authorization = options[:authorization] @fraud_review = options[:fraud_review] @error_code = options[:error_code] diff --git a/test/fixtures.yml b/test/fixtures.yml index cde10232f6c..ffedf50d6f4 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -1434,6 +1434,13 @@ trust_commerce: password: 'password' aggregator_id: 'abc123' +tsys_multipass: + device_id: DEVICE_ID + transaction_key: TRANSACTION_KEY + card: + token: CARD_TOKEN + expiration_date: EXPIRATION_DATE + # Working credentials, no need to replace usa_epay: login: '4EoZ5U2Q55j976W7eplC71i6b7kn4pcV' diff --git a/test/remote/gateways/remote_tsys_multipass_test.rb b/test/remote/gateways/remote_tsys_multipass_test.rb new file mode 100644 index 00000000000..6dbad685e98 --- /dev/null +++ b/test/remote/gateways/remote_tsys_multipass_test.rb @@ -0,0 +1,209 @@ +require 'test_helper' + +class RemoteTsysMultipassTest < Test::Unit::TestCase + def setup + @fixtures = fixtures(:tsys_multipass) + @card_token = @fixtures[:card][:token] + @expiration_date = @fixtures[:card][:expiration_date] + + @gateway = TsysMultipassGateway.new( + device_id: @fixtures[:device_id], + transaction_key: @fixtures[:transaction_key] + ) + end + + def test_successful_purchase + purchase_options = { + cardDataSource: 'INTERNET', + transactionAmount: '100', + cardNumber: @card_token, + expirationDate: @expiration_date + } + + response = @gateway.purchase('100', @card_token, purchase_options) + + assert_equal true, response.success? + assert_equal '100', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_purchase + purchase_options = { + cardDataSource: 'INTERNET', + transactionAmount: '101', + cardNumber: @card_token, + expirationDate: 'INVALID VALUE' + } + + response = @gateway.purchase('102', @card_token, purchase_options) + + assert_equal false, response.success? + assert_equal nil, response.authorization + assert_equal nil, response.amount + assert_equal 'F9901', response.error_code + assert_instance_of Response, response + end + + def test_successful_authorize + auth_options = { + cardDataSource: 'INTERNET', + transactionAmount: '103', + cardNumber: @card_token, + expirationDate: @expiration_date + } + + response = @gateway.authorize('103', @card_token, auth_options) + + assert_equal true, response.success? + assert_equal '103', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_authorize + auth_options = { + cardDataSource: 'INTERNET', + transactionAmount: '104', + cardNumber: @card_token, + expirationDate: 'INVALID VALUE' + } + + response = @gateway.authorize('104', @card_token, auth_options) + + assert_equal false, response.success? + assert_equal nil, response.amount + assert_equal 'F9901', response.error_code + assert_instance_of Response, response + end + + def test_successful_capture + # Authorize first + auth_options = { + cardDataSource: 'INTERNET', + transactionAmount: '105', + cardNumber: @card_token, + expirationDate: @expiration_date + } + + auth_id = @gateway.authorize('105', @card_token, auth_options).authorization + + capture_options = { + transactionAmount: '105', + transactionID: auth_id + } + + response = @gateway.capture('105', @card_token, capture_options) + + assert_equal true, response.success? + assert_equal '105', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_capture + capture_options = { + transactionAmount: '106', + transactionID: 'invalidtransactionid' + } + + response = @gateway.capture('106', @card_token, capture_options) + + assert_equal false, response.success? + assert_equal nil, response.amount + assert_equal 'F9901', response.error_code + assert_instance_of Response, response + end + + def test_successful_void + # Authorize first + auth_options = { + cardDataSource: 'INTERNET', + transactionAmount: '107', + cardNumber: @card_token, + expirationDate: @expiration_date + } + + auth_id = @gateway.authorize('107', @card_token, auth_options).authorization + + void_options = { + transactionAmount: '107', + transactionID: auth_id + } + + response = @gateway.void(@card_token, void_options) + + assert_equal true, response.success? + assert_equal '107', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_void + void_options = { + transactionAmount: '108', + transactionID: 'invalid auth id' + } + + response = @gateway.void(@card_token, void_options) + + assert_equal false, response.success? + assert_equal nil, response.amount + assert_equal 'F9901', response.error_code + assert_instance_of Response, response + end + + def test_successful_refund + # Authorize first + auth_options = { + cardDataSource: 'INTERNET', + transactionAmount: '109', + cardNumber: @card_token, + expirationDate: @expiration_date + } + + auth_id = @gateway.authorize('109', @card_token, auth_options).authorization + + refund_options = { + transactionAmount: '109', + transactionID: auth_id + } + + response = @gateway.refund('109', @card_token, refund_options) + + assert_equal true, response.success? + assert_equal '109', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_refund + # Authorize first + purchase_options = { + cardDataSource: 'INTERNET', + transactionAmount: '110', + cardNumber: @card_token, + expirationDate: @expiration_date + } + + auth_id = @gateway.purchase('110', @card_token, purchase_options).authorization + + refund_options = { + transactionAmount: '10000', # More amount than purchased + transactionID: auth_id + } + + response = @gateway.refund('10000', @card_token, refund_options) + + assert_equal false, response.success? + assert_equal nil, response.amount + assert_equal 'D0005', response.error_code + assert_instance_of Response, response + end + + def test_supports_scrubbing + is_scrubbing_supported = @gateway.supports_scrubbing? + + assert_equal true, is_scrubbing_supported + end +end diff --git a/test/unit/gateways/tsys_multipass_test.rb b/test/unit/gateways/tsys_multipass_test.rb new file mode 100644 index 00000000000..19d65669e1e --- /dev/null +++ b/test/unit/gateways/tsys_multipass_test.rb @@ -0,0 +1,312 @@ +require 'test_helper' + +class TsysMultipassTest < Test::Unit::TestCase + include CommStub + + def setup + @card_token = 'asdsWPabcabcqsdfsd' + @amount = 100 + @expiration_date = '122020' + @auth_id = 'XYZabc' + + @gateway = TsysMultipassGateway.new( + device_id: 'device_id', + transaction_key: 'reg_key' + ) + + @auth_options = { + cardDataSource: 'INTERNET', + transactionAmount: @amount, + cardNumber: @card_token, + expirationDate: @expiration_date + } + + @purchase_options = { + cardDataSource: 'INTERNET', + transactionAmount: @amount, + cardNumber: @card_token, + expirationDate: @expiration_date + } + + @capture_options = { + transactionAmount: @amount.to_s, + transactionID: @auth_id + } + + @refund_options = { + transactionAmount: @amount.to_s, + transactionID: @auth_id + } + + @void_options = { + transactionAmount: @amount.to_s, + transactionID: @auth_id + } + end + + def test_successful_purchase + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(successful_purchase_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.purchase(@amount, @card_token, @purchase_options) + + assert_equal true, response.success? + assert_equal '12345678', response.authorization + assert_equal '100', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_purchase + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(failed_purchase_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.authorize(@amount, @card_token, @purchase_options) + + assert_equal false, response.success? + assert_equal nil, response.authorization + assert_equal nil, response.amount + assert_equal 'F0001', response.error_code + assert_instance_of Response, response + end + + def test_successful_authorize + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(successful_authorize_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.authorize(@amount, @card_token, @auth_options) + + assert_equal true, response.success? + assert_equal '12345678', response.authorization + assert_equal '100', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_authorize + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(failed_authorize_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.authorize(@amount, @card_token, @auth_options) + + assert_equal false, response.success? + assert_equal nil, response.authorization + assert_equal nil, response.amount + assert_equal 'F9901', response.error_code + assert_instance_of Response, response + end + + def test_successful_capture + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(successful_capture_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.capture(@amount, @auth_id, @capture_options) + + assert_equal true, response.success? + assert_equal '21462680', response.authorization + assert_equal '1100', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_capture + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(failed_capture_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.capture(@amount, @auth_id, @capture_options) + + assert_equal false, response.success? + assert_equal nil, response.authorization + assert_equal nil, response.amount + assert_equal 'F9901', response.error_code + assert_instance_of Response, response + end + + def test_successful_void + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(successful_void_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.void(@auth_id, @void_options) + + assert_equal true, response.success? + assert_equal '21468076', response.authorization + assert_equal '10000', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_void + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(failed_void_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.void(@auth_id, @void_options) + + assert_equal false, response.success? + assert_equal nil, response.authorization + assert_equal nil, response.amount + assert_equal 'D0004', response.error_code + assert_instance_of Response, response + end + + def test_successful_refund + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(successful_refund_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.refund(@amount, @auth_id, @refund_options) + + assert_equal true, response.success? + assert_equal '21468126', response.authorization + assert_equal '11250', response.amount + assert_equal '', response.error_code + assert_instance_of Response, response + end + + def test_failed_refund + # Stubbing external response + response_obj = mock() + response_obj.stubs(:body).returns(failed_refund_response) + + @gateway.expects(:response).returns(response_obj) + + response = @gateway.refund(@amount, @auth_id, @refund_options) + + assert_equal false, response.success? + assert_equal nil, response.authorization + assert_equal nil, response.amount + assert_equal 'D0005', response.error_code + assert_instance_of Response, response + end + + def test_supports_scrubbing + is_scrubbing_supported = @gateway.supports_scrubbing? + + assert_equal true, is_scrubbing_supported + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + %({"SaleResponse":{"status":"PASS","responseCode":"A0000","responseMessage":"Success","authCode":"TAS111","hostReferenceNumber":"12345678912345","hostResponseCode":"00","taskID":"12345678","transactionID":"12345678","transactionTimestamp":"2019-12-04T13:17:47","transactionAmount":"100","processedAmount":"100","totalAmount":"100","addressVerificationCode":"0","cardType":"V","maskedCardNumber":"5439","token":"ABCabcssdf5439","expirationDate":"122020","commercialCard":"0","aci":"N","cardTransactionIdentifier":"000000000123456","customerReceipt":"DUMMY RECEIPT TEXT" }}) + end + + def failed_purchase_response + %({"SaleResponse":{"status":"FAIL","responseCode":"F0001","responseMessage":"Error Message"}}) + end + + def successful_authorize_response + %({"AuthResponse":{"status":"PASS","responseCode":"A0000","responseMessage":"Success","authCode":"TAS111","hostReferenceNumber":"12345678912345","hostResponseCode":"00","taskID":"12345678","transactionID":"12345678","transactionTimestamp":"2019-12-04T13:17:47","transactionAmount":"100","processedAmount":"100","totalAmount":"100","addressVerificationCode":"0","cardType":"V","maskedCardNumber":"5439","token":"ABCabcssdf5439","expirationDate":"122020","commercialCard":"0","aci":"N","cardTransactionIdentifier":"000000000123456","customerReceipt":"DUMMY RECEIPT TEXT" }}) + end + + def failed_authorize_response + %({"AuthResponse":{"status":"FAIL","responseCode":"F9901","responseMessage":"Error Message"}}) + end + + def successful_capture_response + %({"CaptureResponse":{"status":"PASS", "responseCode":"A0000", "responseMessage":"Success", "authCode":"TAS817", "cardType":"V", "taskID":"21160272", "transactionID":"21462680", "transactionTimestamp":"2019-12-12T17:37:20", "transactionAmount":"1100", "totalAmount":"1100", "customerReceipt":"DUMMY RECEIPT BODY"}}) + end + + def failed_capture_response + %({"CaptureResponse":{"status":"FAIL", "responseCode":"F9901", "responseMessage":"Error Message"}}) + end + + def successful_void_response + %({"VoidResponse":{"status":"PASS","responseCode":"A0000","responseMessage":"Success","authCode":"TAS554","hostReferenceNumber":"934720500625","hostResponseCode":"00","taskID":"21165594","transactionID":"21468076","transactionTimestamp":"2019-12-13T13:02:04","orderNumber":"21468074","externalReferenceID":"21468074","transactionAmount":"10000","voidedAmount":"10000","cardType":"V","maskedCardNumber":"5439","customerReceipt":"DUMMY RECEIPT BODY"}}) + end + + def failed_void_response + %({"VoidResponse":{"status":"FAIL","responseCode":"D0004","responseMessage":"Error Message"}}) + end + + def successful_refund_response + %({"ReturnResponse":{"status":"PASS","responseCode":"A0014","responseMessage":"Return requested, Void successful","authCode":"TAS638","hostReferenceNumber":"934720500671","hostResponseCode":"00","taskID":"21164738","transactionID":"21468126","transactionTimestamp":"2019-12-13T13:08:59","orderNumber":"21468126","externalReferenceID":"21468126","transactionAmount":"11250","returnedAmount":"11250","cardType":"V","maskedCardNumber":"5439","customerReceipt":"DUMMY RECEIPT BODY"}}) + end + + def failed_refund_response + %({"ReturnResponse":{"status":"FAIL","responseCode":"D0005","responseMessage":"Error Message"}}) + end + + def transcript + <<-PRE_SCRUBBED + opening connection to stagegw.transnox.com:443... +opened +starting SSL for stagegw.transnox.com:443... +SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384 +<- "POST /servlets/TransNox_API_Server HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: stagegw.transnox.com\r\nContent-Length: 241\r\n\r\n" +<- "{\"Auth\":{\"deviceID\":\"88700000321502\",\"transactionKey\":\"RRTSRFXLE8L5L64KSINCYEANQ0UNM5U0\",\"cardDataSource\":\"INTERNET\",\"transactionAmount\":\"10000\",\"cardNumber\":\"Py2ARW2LJpbd5439\",\"expirationDate\":\"12/20\",\"softDescriptor\":\"DUMMY DATA FOR NOW\"}}" +-> "HTTP/1.1 200 \r\n" +-> "Date: Fri, 13 Dec 2019 21:49:04 GMT\r\n" +-> "Content-Type: application/json;charset=ISO-8859-1\r\n" +-> "Set-Cookie: TS01bcb3a9=01e23550884a78befd6dc913284f327d61dec76e50caa3fe81aa15e8804acdab14e80a85ad3bc36c28f64de8667bf10feefc1638f6; Path=/; Secure; HTTPOnly\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "\r\n" +-> "a87\r\n" +reading 2695 bytes... +-> "{\"AuthResponse\":{\"status\":\"PASS\",\"responseCode\":\"A0000\",\"responseMessage\":\"Success\",\"authCode\":\"TAS989\",\"hostReferenceNumber\":\"934721501245\",\"hostResponseCode\":\"00\",\"taskID\":\"21165218\",\"transactionID\":\"21469132\",\"transactionTimestamp\":\"2019-12-13T14:49:03\",\"transactionAmount\":\"10000\",\"processedAmount\":\"10000\",\"totalAmount\":\"10000\",\"addressVerificationCode\":\"0\",\"cardType\":\"V\",\"maskedCardNumber\":\"5439\",\"token\":\"Py2ARW2LJpbd5439\",\"expirationDate\":\"122020\",\"commercialCard\":\"0\",\"aci\":\"N\",\"cardTransactionIdentifier\":\"000000000591089\",\"customerReceipt\":\"DUMMY RECEIPT\n" +read 2695 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn keep-alive + PRE_SCRUBBED + end + + def scrubbed_transcript + <<-POST_SCRUBBED + opening connection to stagegw.transnox.com:443... +opened +starting SSL for stagegw.transnox.com:443... +SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384 +<- "POST /servlets/TransNox_API_Server HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: stagegw.transnox.com\r\nContent-Length: 241\r\n\r\n" +<- "{\"Auth\":{\"deviceID\":[FILTERED],\"transactionKey\":[FILTERED],\"cardDataSource\":\"INTERNET\",\"transactionAmount\":\"10000\",\"cardNumber\":[FILTERED],\"expirationDate\":[FILTERED],\"softDescriptor\":\"DUMMY DATA FOR NOW\"}}" +-> "HTTP/1.1 200 \r\n" +-> "Date: Fri, 13 Dec 2019 21:49:04 GMT\r\n" +-> "Content-Type: application/json;charset=ISO-8859-1\r\n" +-> "Set-Cookie: TS01bcb3a9=01e23550884a78befd6dc913284f327d61dec76e50caa3fe81aa15e8804acdab14e80a85ad3bc36c28f64de8667bf10feefc1638f6; Path=/; Secure; HTTPOnly\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "\r\n" +-> "a87\r\n" +reading 2695 bytes... +-> "{\"AuthResponse\":{\"status\":\"PASS\",\"responseCode\":\"A0000\",\"responseMessage\":\"Success\",\"authCode\":\"TAS989\",\"hostReferenceNumber\":\"934721501245\",\"hostResponseCode\":\"00\",\"taskID\":\"21165218\",\"transactionID\":\"21469132\",\"transactionTimestamp\":\"2019-12-13T14:49:03\",\"transactionAmount\":\"10000\",\"processedAmount\":\"10000\",\"totalAmount\":\"10000\",\"addressVerificationCode\":\"0\",\"cardType\":\"V\",\"maskedCardNumber\":\"5439\",\"token\":[FILTERED],\"expirationDate\":[FILTERED],\"commercialCard\":\"0\",\"aci\":\"N\",\"cardTransactionIdentifier\":\"000000000591089\",\"customerReceipt\":\"DUMMY RECEIPT\n" +read 2695 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn keep-alive + POST_SCRUBBED + end +end