lib/active_merchant/billing/gateways/culqi.rb
require 'digest/md5'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# Important note:
# ===
# Culqi merchant accounts are configured for either purchase or auth/capture
# modes. This is configured by Culqi when setting up a merchant account and
# largely depends on the transaction acquiring bank. Be sure to understand how
# your account was configured prior to using this gateway.
class CulqiGateway < Gateway
self.display_name = 'Culqi'
self.homepage_url = 'https://www.culqi.com'
self.test_url = 'https://staging.paymentz.com/transaction/'
self.live_url = 'https://secure.culqi.com/transaction/'
self.supported_countries = ['PE']
self.default_currency = 'PEN'
self.money_format = :dollars
self.supported_cardtypes = %i[visa master diners_club american_express]
def initialize(options = {})
requires!(options, :merchant_id, :terminal_id, :secret_key)
super
end
def purchase(amount, payment_method, options = {})
authorize(amount, payment_method, options)
end
def authorize(amount, payment_method, options = {})
if payment_method.is_a?(String)
action = :tokenpay
else
action = :authorize
end
post = {}
add_credentials(post)
add_invoice(action, post, amount, options)
add_payment_method(post, payment_method, action, options)
add_customer_data(post, options)
add_checksum(action, post)
commit(action, post)
end
def capture(amount, authorization, options = {})
action = :capture
post = {}
add_credentials(post)
add_invoice(action, post, amount, options)
add_reference(post, authorization)
add_checksum(action, post)
commit(action, post)
end
def void(authorization, options = {})
action = :void
post = {}
add_credentials(post)
add_invoice(action, post, nil, options)
add_reference(post, authorization)
add_checksum(action, post)
commit(action, post)
end
def refund(amount, authorization, options = {})
action = :refund
post = {}
add_credentials(post)
add_invoice(action, post, amount, options)
add_reference(post, authorization)
add_checksum(action, post)
commit(action, post)
end
def verify(credit_card, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(1000, credit_card, options) }
r.process(:ignore_result) { void(r.authorization, options) }
end
end
def verify_credentials
response = void('0', order_id: '0')
response.message.include? 'Transaction not found'
end
def store(credit_card, options = {})
action = :tokenize
post = {}
post[:partnerid] = options[:partner_id] if options[:partner_id]
post[:cardholderid] = options[:cardholder_id] if options[:cardholder_id]
add_credentials(post)
add_payment_method(post, credit_card, action, options)
add_customer_data(post, options)
add_checksum(action, post)
commit(action, post)
end
def invalidate(authorization, options = {})
action = :invalidate
post = {}
post[:partnerid] = options[:partner_id] if options[:partner_id]
add_credentials(post)
post[:token] = authorization
add_checksum(action, post)
commit(action, post)
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((cardnumber=)\d+), '\1[FILTERED]').
gsub(%r((cvv=)\d+), '\1[FILTERED]')
end
private
def add_credentials(post)
post[:toid] = @options[:merchant_id]
post[:totype] = 'Culqi'
post[:terminalid] = @options[:terminal_id]
post[:language] = 'ENG'
end
def add_invoice(action, post, money, options)
case action
when :capture
post[:captureamount] = amount(money)
when :refund
post[:refundamount] = amount(money)
post[:reason] = 'none'
else
post[:amount] = amount(money)
end
post[:description] = options[:order_id]
post[:redirecturl] = options[:redirect_url] || 'http://www.example.com'
end
def add_payment_method(post, payment_method, action, options)
if payment_method.is_a?(String)
post[:token] = payment_method
post[:cvv] = options[:cvv] if options[:cvv]
else
post[:cardnumber] = payment_method.number
post[:cvv] = payment_method.verification_value
post[:firstname], post[:lastname] = payment_method.name.split(' ')
if action == :tokenize
post[:expirymonth] = format(payment_method.month, :two_digits)
post[:expiryyear] = format(payment_method.year, :four_digits)
else
post[:expiry_month] = format(payment_method.month, :two_digits)
post[:expiry_year] = format(payment_method.year, :four_digits)
end
end
end
def add_customer_data(post, options)
post[:emailaddr] = options[:email] || 'unspecified@example.com'
if (billing_address = options[:billing_address] || options[:address])
post[:street] = [billing_address[:address1], billing_address[:address2]].join(' ')
post[:city] = billing_address[:city]
post[:state] = billing_address[:state]
post[:countrycode] = billing_address[:country]
post[:zip] = billing_address[:zip]
post[:telno] = billing_address[:phone]
post[:telnocc] = options[:telephone_country_code] || '051'
end
end
def add_checksum(action, post)
checksum_elements =
case action
when :capture then [post[:toid], post[:trackingid], post[:captureamount], @options[:secret_key]]
when :void then [post[:toid], post[:description], post[:trackingid], @options[:secret_key]]
when :refund then [post[:toid], post[:trackingid], post[:refundamount], @options[:secret_key]]
when :tokenize then [post[:partnerid], post[:cardnumber], post[:cvv], @options[:secret_key]]
when :invalidate then [post[:partnerid], post[:token], @options[:secret_key]]
else [post[:toid], post[:totype], post[:amount], post[:description], post[:redirecturl],
post[:cardnumber] || post[:token], @options[:secret_key]]
end
post[:checksum] = Digest::MD5.hexdigest(checksum_elements.compact.join('|'))
end
def add_reference(post, authorization)
post[:trackingid] = authorization
end
ACTIONS = {
authorize: 'SingleCallGenericServlet',
capture: 'SingleCallGenericCaptureServlet',
void: 'SingleCallGenericVoid',
refund: 'SingleCallGenericReverse',
tokenize: 'SingleCallTokenServlet',
invalidate: 'SingleCallInvalidateToken',
tokenpay: 'SingleCallTokenTransaction'
}
def commit(action, params)
response =
begin
parse(ssl_post(url + ACTIONS[action], post_data(action, params), headers))
rescue ResponseError => e
parse(e.response.body)
end
success = success_from(response)
Response.new(
success,
message_from(response),
response,
authorization: success ? authorization_from(response) : nil,
cvv_result: success ? cvvresult_from(response) : nil,
error_code: success ? nil : error_from(response),
test: test?
)
end
def headers
{
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
}
end
def post_data(action, params)
params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
end
def url
test? ? test_url : live_url
end
def parse(body)
JSON.parse(body)
rescue JSON::ParserError
message = 'Invalid JSON response received from CulqiGateway. Please contact CulqiGateway if you continue to receive this message.'
message += "(The raw response returned by the API was #{body.inspect})"
{
'status' => 'N',
'statusdescription' => message
}
end
def success_from(response)
response['status'] == 'Y'
end
def message_from(response)
response['statusdescription'] || response['statusDescription']
end
def authorization_from(response)
response['trackingid'] || response['token']
end
def cvvresult_from(response)
CVVResult.new(response['cvvresult'])
end
def error_from(response)
response['resultcode']
end
end
end
end