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 = [: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; [post[:toid], post[:trackingid], post[:captureamount], @options[:secret_key]] when :void; [post[:toid], post[:description], post[:trackingid], @options[:secret_key]] when :refund; [post[:toid], post[:trackingid], post[:refundamount], @options[:secret_key]] when :tokenize; [post[:partnerid], post[:cardnumber], post[:cvv], @options[:secret_key]] when :invalidate; [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) begin 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 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