lib/active_merchant/billing/gateways/deepstack.rb
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class DeepstackGateway < Gateway
self.test_url = 'https://api.sandbox.deepstack.io'
self.live_url = 'https://api.deepstack.io'
self.supported_countries = ['US']
self.default_currency = 'USD'
self.supported_cardtypes = %i[visa master american_express discover]
self.money_format = :cents
self.homepage_url = 'https://deepstack.io/'
self.display_name = 'Deepstack Gateway'
STANDARD_ERROR_CODE_MAPPING = {}
def initialize(options = {})
requires!(options, :publishable_api_key, :app_id, :shared_secret)
@publishable_api_key, @app_id, @shared_secret = options.values_at(:publishable_api_key, :app_id, :shared_secret)
super
end
def purchase(money, payment, options = {})
post = {}
add_payment(post, payment, options)
add_order(post, money, options)
add_purchase_capture(post)
add_address(post, payment, options)
add_customer_data(post, options)
commit('sale', post)
end
def authorize(money, payment, options = {})
post = {}
add_payment(post, payment, options)
add_order(post, money, options)
add_address(post, payment, options)
add_customer_data(post, options)
commit('auth', post)
end
def capture(money, authorization, options = {})
post = {}
add_invoice(post, money, authorization, options)
commit('capture', post)
end
def refund(money, authorization, options = {})
post = {}
add_invoice(post, money, authorization, options)
commit('refund', post)
end
def void(money, authorization, options = {})
post = {}
add_invoice(post, money, authorization, options)
commit('void', post)
end
def verify(credit_card, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(100, credit_card, options) }
r.process(:ignore_result) { void(0, r.authorization, options) }
end
end
def get_token(credit_card, options = {})
post = {}
add_payment_instrument(post, credit_card, options)
add_address_payment_instrument(post, credit_card, options)
commit('gettoken', post)
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]').
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
gsub(%r((Hmac: )[\w=]+), '\1[FILTERED]').
gsub(%r((\\"account_number\\":\\")[\w*]+), '\1[FILTERED]').
gsub(%r((\\"cvv\\":\\")\w+), '\1[FILTERED]').
gsub(%r((\\"expiration\\":\\")\w+), '\1[FILTERED]')
end
private
def add_customer_data(post, options)
post[:meta] ||= {}
add_shipping(post, options) if options.key?(:shipping_address)
post[:meta][:client_customer_id] = options[:customer] if options[:customer]
post[:meta][:client_transaction_id] = options[:order_id] if options[:order_id]
post[:meta][:client_transaction_description] = options[:description] if options[:description]
post[:meta][:client_invoice_id] = options[:invoice] if options[:invoice]
post[:meta][:card_holder_ip_address] = options[:ip] if options[:ip]
end
def add_address(post, creditcard, options)
return post unless options.key?(:address) || options.key?(:billing_address)
billing_address = options[:address] || options[:billing_address]
post[:source] ||= {}
post[:source][:billing_contact] = {}
post[:source][:billing_contact][:first_name] = billing_address[:first_name] if billing_address[:first_name]
post[:source][:billing_contact][:last_name] = billing_address[:last_name] if billing_address[:last_name]
post[:source][:billing_contact][:phone] = billing_address[:phone] if billing_address[:phone]
post[:source][:billing_contact][:email] = options[:email] if options[:email]
post[:source][:billing_contact][:address] = {}
post[:source][:billing_contact][:address][:line_1] = billing_address[:address1] if billing_address[:address1]
post[:source][:billing_contact][:address][:line_2] = billing_address[:address2] if billing_address[:address2]
post[:source][:billing_contact][:address][:city] = billing_address[:city] if billing_address[:city]
post[:source][:billing_contact][:address][:state] = billing_address[:state] if billing_address[:state]
post[:source][:billing_contact][:address][:postal_code] = billing_address[:zip] if billing_address[:zip]
post[:source][:billing_contact][:address][:country_code] = billing_address[:country] if billing_address[:country]
end
def add_address_payment_instrument(post, creditcard, options)
return post unless options.key?(:address) || options.key?(:billing_address)
billing_address = options[:address] || options[:billing_address]
post[:source] = {} unless post.key?(:payment_instrument)
post[:payment_instrument][:billing_contact] = {}
post[:payment_instrument][:billing_contact][:first_name] = billing_address[:first_name] if billing_address[:first_name]
post[:payment_instrument][:billing_contact][:last_name] = billing_address[:last_name] if billing_address[:last_name]
post[:payment_instrument][:billing_contact][:phone] = billing_address[:phone] if billing_address[:phone]
post[:payment_instrument][:billing_contact][:email] = billing_address[:email] if billing_address[:email]
post[:payment_instrument][:billing_contact][:address] = {}
post[:payment_instrument][:billing_contact][:address][:line_1] = billing_address[:address1] if billing_address[:address1]
post[:payment_instrument][:billing_contact][:address][:line_2] = billing_address[:address2] if billing_address[:address2]
post[:payment_instrument][:billing_contact][:address][:city] = billing_address[:city] if billing_address[:city]
post[:payment_instrument][:billing_contact][:address][:state] = billing_address[:state] if billing_address[:state]
post[:payment_instrument][:billing_contact][:address][:postal_code] = billing_address[:zip] if billing_address[:zip]
post[:payment_instrument][:billing_contact][:address][:country_code] = billing_address[:country] if billing_address[:country]
end
def add_shipping(post, options = {})
return post unless options.key?(:shipping_address)
shipping = options[:shipping_address]
post[:meta][:shipping_info] = {}
post[:meta][:shipping_info][:first_name] = shipping[:first_name] if shipping[:first_name]
post[:meta][:shipping_info][:last_name] = shipping[:last_name] if shipping[:last_name]
post[:meta][:shipping_info][:phone] = shipping[:phone] if shipping[:phone]
post[:meta][:shipping_info][:email] = shipping[:email] if shipping[:email]
post[:meta][:shipping_info][:address] = {}
post[:meta][:shipping_info][:address][:line_1] = shipping[:address1] if shipping[:address1]
post[:meta][:shipping_info][:address][:line_2] = shipping[:address2] if shipping[:address2]
post[:meta][:shipping_info][:address][:city] = shipping[:city] if shipping[:city]
post[:meta][:shipping_info][:address][:state] = shipping[:state] if shipping[:state]
post[:meta][:shipping_info][:address][:postal_code] = shipping[:zip] if shipping[:zip]
post[:meta][:shipping_info][:address][:country_code] = shipping[:country] if shipping[:country]
end
def add_invoice(post, money, authorization, options)
post[:amount] = amount(money)
post[:charge] = authorization
end
def add_payment(post, payment, options)
if payment.kind_of?(String)
post[:source] = {}
post[:source][:type] = 'card_on_file'
post[:source][:card_on_file] = {}
post[:source][:card_on_file][:id] = payment
post[:source][:card_on_file][:cvv] = options[:verification_value] || ''
post[:source][:card_on_file][:customer_id] = options[:customer_id] || ''
# credit card object
elsif payment.respond_to?(:number)
post[:source] = {}
post[:source][:type] = 'credit_card'
post[:source][:credit_card] = {}
post[:source][:credit_card][:account_number] = payment.number
post[:source][:credit_card][:cvv] = payment.verification_value || ''
post[:source][:credit_card][:expiration] = '%02d%02d' % [payment.month, payment.year % 100]
post[:source][:credit_card][:customer_id] = options[:customer_id] || ''
end
end
def add_payment_instrument(post, creditcard, options)
if creditcard.kind_of?(String)
post[:source] = creditcard
return post
end
return post unless creditcard.respond_to?(:number)
post[:payment_instrument] = {}
post[:payment_instrument][:type] = 'credit_card'
post[:payment_instrument][:credit_card] = {}
post[:payment_instrument][:credit_card][:account_number] = creditcard.number
post[:payment_instrument][:credit_card][:expiration] = '%02d%02d' % [creditcard.month, creditcard.year % 100]
post[:payment_instrument][:credit_card][:cvv] = creditcard.verification_value
end
def add_order(post, amount, options)
post[:transaction] ||= {}
post[:transaction][:amount] = amount
post[:transaction][:cof_type] = options.key?(:cof_type) ? options[:cof_type].upcase : 'UNSCHEDULED_CARDHOLDER'
post[:transaction][:capture] = false # Change this in the request (auth/charge)
post[:transaction][:currency_code] = (options[:currency] || currency(amount).upcase)
post[:transaction][:avs] = options[:avs] || true # default avs to true unless told otherwise
post[:transaction][:save_payment_instrument] = options[:save_payment_instrument] || false
end
def add_purchase_capture(post)
post[:transaction] ||= {}
post[:transaction][:capture] = true
end
def parse(body)
return {} if !body || body.empty?
JSON.parse(body)
end
def commit(action, parameters, method = 'POST')
url = (test? ? test_url : live_url)
if no_hmac(action)
request_headers = headers.merge(create_basic(parameters, action))
else
request_headers = headers.merge(create_hmac(parameters, method))
end
request_url = url + get_url(action)
begin
response = parse(ssl_post(request_url, post_data(action, parameters), request_headers))
Response.new(
success_from(response),
message_from(response),
response,
authorization: authorization_from(response),
avs_result: AVSResult.new(code: response['avs_result']),
cvv_result: CVVResult.new(response['cvv_result']),
test: test?,
error_code: error_code_from(response)
)
rescue ResponseError => e
Response.new(
false,
message_from_error(e.response.body),
response_error(e.response.body)
)
rescue JSON::ParserError
Response.new(
false,
message_from(response),
json_error(response)
)
end
end
def headers
{
'Accept' => 'text/plain',
'Content-Type' => 'application/json'
}
end
def response_error(response)
parse(response)
rescue JSON::ParserError
json_error(response)
end
def json_error(response)
msg = 'Invalid response received from the Conekta API.'
msg += " (The raw response returned by the API was #{response.inspect})"
{
'message' => msg
}
end
def success_from(response)
success = false
if response.key?('response_code')
success = response['response_code'] == '00'
# Hack because token/payment instrument methods do not return a response_code
elsif response.key?('id')
success = true if response['id'].start_with?('tok', 'card')
end
return success
end
def message_from(response)
response = JSON.parse(response) if response.is_a?(String)
if response.key?('message')
return response['message']
elsif response.key?('detail')
return response['detail']
end
end
def message_from_error(response)
if response.is_a?(String)
response.gsub!('\\"', '"')
response = JSON.parse(response)
end
if response.key?('detail')
return response['detail']
elsif response.key?('message')
return response['message']
end
end
def authorization_from(response)
response['id']
end
def post_data(action, parameters = {})
return JSON.generate(parameters)
end
def error_code_from(response)
error_code = nil
error_code = response['response_code'] unless success_from(response)
if error = response.dig('detail')
error_code = error
elsif error = response.dig('error')
error_code = error.dig('reason', 'id')
end
error_code
end
def get_url(action)
base = '/api/v1/'
case action
when 'sale'
return base + 'payments/charge'
when 'auth'
return base + 'payments/charge'
when 'capture'
return base + 'payments/capture'
when 'void'
return base + 'payments/refund'
when 'refund'
return base + 'payments/refund'
when 'gettoken'
return base + 'vault/token'
when 'vault'
return base + 'vault/payment-instrument/token'
else
return base + 'noaction'
end
end
def no_hmac(action)
case action
when 'gettoken'
return true
else
return false
end
end
def create_basic(post, method)
return { 'Authorization' => "Bearer #{@publishable_api_key}" }
end
def create_hmac(post, method)
# Need requestDate, requestMethod, Nonce, AppIDKey
app_id_key = @app_id
request_method = method.upcase
uuid = SecureRandom.uuid
request_time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
string_to_hash = "#{app_id_key}|#{request_method}|#{request_time}|#{uuid}|#{JSON.generate(post)}"
signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), Base64.strict_decode64(@shared_secret), string_to_hash)
base64_signature = Base64.strict_encode64(signature)
hmac_header = Base64.strict_encode64("#{app_id_key}|#{request_method}|#{request_time}|#{uuid}|#{base64_signature}")
return { 'hmac' => hmac_header }
end
end
end
end