module ActiveMerchant #:nodoc: module Billing #:nodoc: class CtPaymentGateway < Gateway self.test_url = 'https://test.ctpaiement.ca/v1/' self.live_url = 'https://www.ctpaiement.com/v1/' self.supported_countries = ['US', 'CA'] self.default_currency = 'CAD' self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] self.homepage_url = 'http://www.ct-payment.com/' self.display_name = 'CT Payment' STANDARD_ERROR_CODE_MAPPING = { '14' => STANDARD_ERROR_CODE[:invalid_number], '05' => STANDARD_ERROR_CODE[:card_declined], 'M6' => STANDARD_ERROR_CODE[:card_declined], '9068' => STANDARD_ERROR_CODE[:incorrect_number], '9067' => STANDARD_ERROR_CODE[:incorrect_number] } CARD_BRAND = { 'american_express' => 'A', 'master' => 'M', 'diners_club' => 'I', 'visa' => 'V', 'discover' => 'O' } def initialize(options={}) requires!(options, :api_key, :company_number, :merchant_number) super end def purchase(money, payment, options={}) requires!(options, :order_id) post = {} add_terminal_number(post, options) add_money(post, money) add_operator_id(post, options) add_invoice(post, money, options) add_payment(post, payment) add_address(post, payment, options) add_customer_data(post, options) payment.is_a?(String) ? commit('purchaseWithToken', post) : commit('purchase', post) end def authorize(money, payment, options={}) requires!(options, :order_id) post = {} add_money(post, money) add_terminal_number(post, options) add_operator_id(post, options) add_invoice(post, money, options) add_payment(post, payment) add_address(post, payment, options) add_customer_data(post, options) payment.is_a?(String) ? commit('preAuthorizationWithToken', post) : commit('preAuthorization', post) end def capture(money, authorization, options={}) requires!(options, :order_id) post = {} add_invoice(post, money, options) add_money(post, money) add_customer_data(post, options) transaction_number, authorization_number, invoice_number = split_authorization(authorization) post[:OriginalTransactionNumber] = transaction_number post[:OriginalAuthorizationNumber] = authorization_number post[:OriginalInvoiceNumber] = invoice_number commit('completion', post) end def refund(money, authorization, options={}) requires!(options, :order_id) post = {} add_invoice(post, money, options) add_money(post, money) add_customer_data(post, options) transaction_number, _, invoice_number = split_authorization(authorization) post[:OriginalTransactionNumber] = transaction_number post[:OriginalInvoiceNumber] = invoice_number commit('refundWithoutCard', post) end def credit(money, payment, options={}) requires!(options, :order_id) post = {} add_terminal_number(post, options) add_money(post, money) add_operator_id(post, options) add_invoice(post, money, options) add_payment(post, payment) add_address(post, payment, options) add_customer_data(post, options) payment.is_a?(String) ? commit('refundWithToken', post) : commit('refund', post) end def void(authorization, options={}) post = {} post[:InputType] = 'I' post[:LanguageCode] = 'E' transaction_number, _, invoice_number = split_authorization(authorization) post[:OriginalTransactionNumber] = transaction_number post[:OriginalInvoiceNumber] = invoice_number add_operator_id(post, options) add_customer_data(post, options) commit('void', post) end def verify(credit_card, options={}) requires!(options, :order_id) post = {} add_terminal_number(post, options) add_operator_id(post, options) add_invoice(post, 0, options) add_payment(post, credit_card) add_address(post, credit_card, options) add_customer_data(post, options) commit('verifyAccount', post) end def store(credit_card, options={}) requires!(options, :email) post = { LanguageCode: 'E', Name: credit_card.name.rjust(50, ' '), Email: options[:email].rjust(240, ' ') } add_operator_id(post, options) add_payment(post, credit_card) add_customer_data(post, options) commit('recur/AddUser', post) end def supports_scrubbing? true end def scrub(transcript) transcript. gsub(%r((&?auth-api-key=)[^&]*)i, '\1[FILTERED]'). gsub(%r((&?payload=)[a-zA-Z%0-9=]+)i, '\1[FILTERED]'). gsub(%r((&?token:)[^&]*)i, '\1[FILTERED]'). gsub(%r((&?cardNumber:)[^&]*)i, '\1[FILTERED]') end private def add_terminal_number(post, options) post[:MerchantTerminalNumber] = options[:merchant_terminal_number] || ' ' * 5 end def add_money(post, money) post[:Amount] = money.to_s.rjust(11, '0') end def add_operator_id(post, options) post[:OperatorID] = options[:operator_id] || '0' * 8 end def add_customer_data(post, options) post[:CustomerNumber] = options[:customer_number] || '0' * 8 end def add_address(post, creditcard, options) if address = options[:billing_address] || options[:address] post[:CardHolderAddress] = "#{address[:address1]} #{address[:address2]} #{address[:city]} #{address[:state]}".rjust(20, ' ') post[:CardHolderPostalCode] = address[:zip].gsub(/\s+/, '').rjust(9, ' ') end end def add_invoice(post, money, options) post[:CurrencyCode] = options[:currency] || (currency(money) if money) post[:InvoiceNumber] = options[:order_id].rjust(12, '0') post[:InputType] = 'I' post[:LanguageCode] = 'E' end def add_payment(post, payment) if payment.is_a?(String) post[:Token] = split_authorization(payment)[3].strip else post[:CardType] = CARD_BRAND[payment.brand] || ' ' post[:CardNumber] = payment.number.rjust(40, ' ') post[:ExpirationDate] = expdate(payment) post[:Cvv2Cvc2Number] = payment.verification_value end end def parse(body) JSON.parse(body) end def split_authorization(authorization) authorization.split(';') end def commit_raw(action, parameters) url = (test? ? test_url : live_url) + action response = parse(ssl_post(url, post_data(action, parameters))) Response.new( success_from(response), message_from(response), response, authorization: authorization_from(response), avs_result: AVSResult.new(code: response['avsStatus']), cvv_result: CVVResult.new(response['cvv2Cvc2Status']), test: test?, error_code: error_code_from(response) ) end def commit(action, parameters) if action == 'void' commit_raw(action, parameters) else MultiResponse.run(true) do |r| r.process { commit_raw(action, parameters) } r.process { split_auth = split_authorization(r.authorization) auth = (action.include?('recur')? split_auth[4] : split_auth[0]) action.include?('recur') ? commit_raw('recur/ack', {ID: auth}) : commit_raw('ack', {TransactionNumber: auth}) } end end end def success_from(response) return true if response['returnCode'] == ' 00' return true if response['returnCode'] == 'true' return true if response['recurReturnCode'] == ' 00' return false end def message_from(response) response['errorDescription'] || response['terminalDisp']&.strip end def authorization_from(response) "#{response['transactionNumber']};#{response['authorizationNumber']};"\ "#{response['invoiceNumber']};#{response['token']};#{response['id']}" end def post_data(action, parameters = {}) parameters[:CompanyNumber] = @options[:company_number] parameters[:MerchantNumber] = @options[:merchant_number] parameters = parameters.collect do |key, value| "#{key}=#{value}" unless value.nil? || value.empty? end.join('&') payload = Base64.strict_encode64(parameters) "auth-api-key=#{@options[:api_key]}&payload=#{payload}".strip end def error_code_from(response) STANDARD_ERROR_CODE_MAPPING[response['returnCode'].strip || response['recurReturnCode'.strip]] unless success_from(response) end end end end