lib/active_merchant/billing/gateways/simetrik.rb
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class SimetrikGateway < Gateway
self.test_url = 'https://payments.sta.simetrik.com/v1'
self.live_url = 'https://payments.simetrik.com/v1'
class_attribute :test_auth_url, :live_auth_url, :test_audience, :live_audience
self.test_auth_url = 'https://tenant-payments-dev.us.auth0.com/oauth/token'
self.live_auth_url = 'https://tenant-payments-prod.us.auth0.com/oauth/token'
self.test_audience = 'https://tenant-payments-dev.us.auth0.com/api/v2/'
self.live_audience = 'https://tenant-payments-prod.us.auth0.com/api/v2/'
self.supported_countries = %w(PE AR)
self.default_currency = 'USD'
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'https://www.simetrik.com'
self.display_name = 'Simetrik'
STANDARD_ERROR_CODE_MAPPING = {
'R101' => STANDARD_ERROR_CODE[:incorrect_number],
'R102' => STANDARD_ERROR_CODE[:invalid_number],
'R103' => STANDARD_ERROR_CODE[:invalid_expiry_date],
'R104' => STANDARD_ERROR_CODE[:invalid_cvc],
'R105' => STANDARD_ERROR_CODE[:expired_card],
'R106' => STANDARD_ERROR_CODE[:incorrect_cvc],
'R107' => STANDARD_ERROR_CODE[:incorrect_pin],
'R201' => STANDARD_ERROR_CODE[:incorrect_zip],
'R202' => STANDARD_ERROR_CODE[:incorrect_address],
'R301' => STANDARD_ERROR_CODE[:card_declined],
'R302' => STANDARD_ERROR_CODE[:processing_error],
'R303' => STANDARD_ERROR_CODE[:call_issuer],
'R304' => STANDARD_ERROR_CODE[:pick_up_card],
'R305' => STANDARD_ERROR_CODE[:processing_error],
'R306' => STANDARD_ERROR_CODE[:processing_error],
'R307' => STANDARD_ERROR_CODE[:processing_error],
'R401' => STANDARD_ERROR_CODE[:config_error],
'R402' => STANDARD_ERROR_CODE[:test_mode_live_card],
'R403' => STANDARD_ERROR_CODE[:unsupported_feature]
}
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
@access_token = options[:access_token] || {}
sign_access_token()
end
def authorize(money, payment, options = {})
requires!(options, :token_acquirer)
post = {}
add_forward_route(post, options)
add_forward_payload(post, money, payment, options)
add_stored_credential(post, options)
commit('authorize', post, { token_acquirer: options[:token_acquirer] })
end
def capture(money, authorization, options = {})
requires!(options, :token_acquirer)
post = {
forward_payload: {
amount: {
total_amount: amount(money).to_f,
currency: (options[:currency] || currency(money))
},
transaction: {
id: authorization
},
acquire_extra_options: options[:acquire_extra_options] || {}
}
}
post[:forward_payload][:amount][:vat] = options[:vat].to_f / 100 if options[:vat]
add_forward_route(post, options)
commit('capture', post, { token_acquirer: options[:token_acquirer] })
end
def refund(money, authorization, options = {})
requires!(options, :token_acquirer)
post = {
forward_payload: {
amount: {
total_amount: amount(money).to_f,
currency: (options[:currency] || currency(money))
},
transaction: {
id: authorization
},
acquire_extra_options: options[:acquire_extra_options] || {}
}
}
post[:forward_payload][:transaction][:comment] = options[:comment] if options[:comment]
add_forward_route(post, options)
commit('refund', post, { token_acquirer: options[:token_acquirer] })
end
def void(authorization, options = {})
requires!(options, :token_acquirer)
post = {
forward_payload: {
transaction: {
id: authorization
},
acquire_extra_options: options[:acquire_extra_options] || {}
}
}
add_forward_route(post, options)
commit('void', post, { token_acquirer: options[:token_acquirer] })
end
def purchase(money, payment, options = {})
requires!(options, :token_acquirer)
post = {}
add_forward_route(post, options)
add_forward_payload(post, money, payment, options)
add_stored_credential(post, options)
commit('charge', post, { token_acquirer: options[:token_acquirer] })
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((Authorization: Bearer ).+), '\1[FILTERED]').
gsub(%r(("client_secret\\?":\\?")\w+), '\1[FILTERED]').
gsub(%r(("number\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("security_code\\?":\\?")\d+), '\1[FILTERED]')
end
private
def add_forward_route(post, options)
forward_route = {}
forward_route[:trace_id] = options[:trace_id] if options[:trace_id]
forward_route[:psp_extra_fields] = options[:psp_extra_fields] || {}
post[:forward_route] = forward_route
end
def add_forward_payload(post, money, payment, options)
forward_payload = {}
add_user(forward_payload, options[:user]) if options[:user]
add_order(forward_payload, money, options)
add_payment_method(forward_payload, payment, options[:payment_method]) if options[:payment_method] || payment
forward_payload[:payment_method] = {} unless forward_payload[:payment_method]
forward_payload[:payment_method][:card] = {} unless forward_payload[:payment_method][:card]
add_address('billing_address', forward_payload[:payment_method][:card], options[:billing_address]) if options[:billing_address]
add_three_ds_fields(forward_payload[:authentication] = {}, options[:three_ds_fields]) if options[:three_ds_fields]
add_sub_merchant(forward_payload, options[:sub_merchant]) if options[:sub_merchant]
forward_payload[:acquire_extra_options] = options[:acquire_extra_options] || {}
post[:forward_payload] = forward_payload
end
def add_sub_merchant(post, sub_merchant_options)
sub_merchant = {}
sub_merchant[:merchant_id] = sub_merchant_options[:merchant_id] if sub_merchant_options[:merchant_id]
sub_merchant[:extra_params] = sub_merchant_options[:extra_params] if sub_merchant_options[:extra_params]
sub_merchant[:mcc] = sub_merchant_options[:mcc] if sub_merchant_options[:mcc]
sub_merchant[:name] = sub_merchant_options[:name] if sub_merchant_options[:name]
sub_merchant[:address] = sub_merchant_options[:address] if sub_merchant_options[:address]
sub_merchant[:postal_code] = sub_merchant_options[:postal_code] if sub_merchant_options[:postal_code]
sub_merchant[:url] = sub_merchant_options[:url] if sub_merchant_options[:url]
sub_merchant[:phone_number] = sub_merchant_options[:phone_number] if sub_merchant_options[:phone_number]
post[:sub_merchant] = sub_merchant
end
def add_payment_method(post, payment, payment_method_options)
payment_method = {}
opts = nil
opts = payment_method_options[:card] if payment_method_options
add_card(payment_method, payment, opts) if opts || payment
post[:payment_method] = payment_method
end
def add_three_ds_fields(post, three_ds_options)
three_ds = {}
three_ds[:version] = three_ds_options[:version] if three_ds_options[:version]
three_ds[:eci] = three_ds_options[:eci] if three_ds_options[:eci]
three_ds[:cavv] = three_ds_options[:cavv] if three_ds_options[:cavv]
three_ds[:ds_transaction_id] = three_ds_options[:ds_transaction_id] if three_ds_options[:ds_transaction_id]
three_ds[:acs_transaction_id] = three_ds_options[:acs_transaction_id] if three_ds_options[:acs_transaction_id]
three_ds[:xid] = three_ds_options[:xid] if three_ds_options[:xid]
three_ds[:enrolled] = three_ds_options[:enrolled] if three_ds_options[:enrolled]
three_ds[:cavv_algorithm] = three_ds_options[:cavv_algorithm] if three_ds_options[:cavv_algorithm]
three_ds[:directory_response_status] = three_ds_options[:directory_response_status] if three_ds_options[:directory_response_status]
three_ds[:authentication_response_status] = three_ds_options[:authentication_response_status] if three_ds_options[:authentication_response_status]
three_ds[:three_ds_server_trans_id] = three_ds_options[:three_ds_server_trans_id] if three_ds_options[:three_ds_server_trans_id]
post[:three_ds_fields] = three_ds
end
def add_card(post, card, card_options = {})
card_hash = {}
card_hash[:number] = card.number
card_hash[:exp_month] = card.month
card_hash[:exp_year] = card.year
card_hash[:security_code] = card.verification_value
card_hash[:type] = card.brand
card_hash[:holder_first_name] = card.first_name
card_hash[:holder_last_name] = card.last_name
post[:card] = card_hash
end
def add_user(post, user_options)
user = {}
user[:id] = user_options[:id] if user_options[:id]
user[:email] = user_options[:email] if user_options[:email]
post[:user] = user
end
def add_stored_credential(post, options)
return unless options[:stored_credential]
check_initiator = %w[merchant cardholder].any? { |item| item == options[:stored_credential][:initiator] }
check_reason_type = %w[recurring installment unscheduled].any? { |item| item == options[:stored_credential][:reason_type] }
post[:forward_payload][:authentication] = {} unless post[:forward_payload].key?(:authentication)
post[:forward_payload][:authentication][:stored_credential] = options[:stored_credential] if check_initiator && check_reason_type
end
def add_order(post, money, options)
return unless options[:order] || money
order = {}
order_options = options[:order] || {}
order[:id] = options[:order_id] if options[:order_id]
order[:description] = options[:description] if options[:description]
order[:installments] = order_options[:installments].to_i if order_options[:installments]
order[:datetime_local_transaction] = order_options[:datetime_local_transaction] if order_options[:datetime_local_transaction]
add_amount(order, money, options)
add_address('shipping_address', order, options)
post[:order] = order
end
def add_amount(post, money, options)
amount_obj = {}
amount_obj[:total_amount] = amount(money).to_f
amount_obj[:currency] = (options[:currency] || currency(money))
amount_obj[:vat] = options[:vat].to_f / 100 if options[:vat]
post[:amount] = amount_obj
end
def add_address(tag, post, options)
return unless address_options = options[:shipping_address]
address = {}
address[:name] = address_options[:name] if address_options[:name]
address[:address1] = address_options[:address1] if address_options[:address1]
address[:address2] = address_options[:address2] if address_options[:address2]
address[:company] = address_options[:company] if address_options[:company]
address[:city] = address_options[:city] if address_options[:city]
address[:state] = address_options[:state] if address_options[:state]
address[:zip] = address_options[:zip] if address_options[:zip]
address[:country] = address_options[:country] if address_options[:country]
address[:phone] = address_options[:phone] if address_options[:phone]
address[:fax] = address_options[:fax] if address_options[:fax]
post[tag] = address
end
def parse(body)
JSON.parse(body)
end
def commit(action, parameters, url_params = {})
begin
response = JSON.parse ssl_post(url(action, url_params), post_data(parameters), authorized_headers())
rescue ResponseError => e
case e.response.code.to_i
when 400...499
response = JSON.parse e.response.body
else
raise e
end
end
Response.new(
success_from(response['code']),
message_from(response),
response,
authorization: authorization_from(response),
avs_result: AVSResult.new(code: avs_code_from(response)),
cvv_result: CVVResult.new(cvv_code_from(response)),
test: test?,
error_code: error_code_from(response)
)
end
def avs_code_from(response)
response['avs_result']
end
def cvv_code_from(response)
response['cvv_result']
end
def success_from(code)
code == 'S001'
end
def message_from(response)
response['message']
end
def url(action, url_params)
"#{test? ? test_url : live_url}/#{url_params[:token_acquirer]}/#{action}"
end
def post_data(data = {})
data.to_json
end
def authorization_from(response)
response['simetrik_authorization_id']
end
def error_code_from(response)
STANDARD_ERROR_CODE_MAPPING[response['code']] unless success_from(response['code'])
end
def authorized_headers
{
'content-Type' => 'application/json',
'Authorization' => "Bearer #{sign_access_token()}"
}
end
# if this method is refactored, ensure that the client_secret is properly scrubbed
def sign_access_token
fetch_access_token() if Time.new.to_i > (@access_token[:expires_at] || 0) + 10
@access_token[:access_token]
end
def auth_url
(test? ? test_auth_url : live_auth_url)
end
def fetch_access_token
login_info = {}
login_info[:client_id] = @options[:client_id]
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
begin
raw_response = ssl_post(auth_url(), login_info.to_json, {
'content-Type' => 'application/json'
})
rescue ResponseError => e
raise OAuthResponseError.new(e)
else
response = parse(raw_response)
@access_token[:access_token] = response['access_token']
@access_token[:expires_at] = Time.new.to_i + response['expires_in']
end
end
end
end
end