Skip to content

Commit

Permalink
Enable Tsys multipass Payment Gateway (#1)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rahuldess authored Dec 16, 2019
1 parent 6e115b5 commit b5c9a33
Show file tree
Hide file tree
Showing 5 changed files with 720 additions and 1 deletion.
190 changes: 190 additions & 0 deletions lib/active_merchant/billing/gateways/tsys_multipass.rb
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion lib/active_merchant/billing/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
attr_reader :params, :message, :test, :amount, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization

def success?
@success
Expand All @@ -21,6 +21,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]
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,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'
Expand Down
Loading

0 comments on commit b5c9a33

Please sign in to comment.