module ActiveMerchant #:nodoc: module Billing #:nodoc: class FirstPayGateway < Gateway class FirstPayPostData < PostData # Fields that will be sent even if they are blank self.required_fields = [ :action, :amount, :trackid ] end # both URLs are IP restricted TEST_URL = 'https://apgcert.first-pay.com/AcqENGIN/SecureCapture' LIVE_URL = 'https://acqengin.first-pay.com/AcqENGIN/SecureCapture' # The countries the gateway supports merchants from as 2 digit ISO country codes self.supported_countries = ['US'] # The card types supported by the payment gateway self.supported_cardtypes = [:visa, :master] # The homepage URL of the gateway self.homepage_url = 'http://www.first-pay.com' # The name of the gateway self.display_name = 'First Pay' # all transactions are in cents self.money_format = :cents ACTIONS = { 'sale' => 1, 'credit' => 2, 'void' => 3 } def initialize(options = {}) requires!(options, :login, :password) @options = options super end def purchase(money, creditcard, options = {}) post = FirstPayPostData.new add_invoice(post, options) add_creditcard(post, creditcard) add_address(post, options) add_customer_data(post, options) commit('sale', money, post) end def refund(money, reference, options = {}) requires!(options, :credit_card) post = FirstPayPostData.new add_invoice(post, options) add_creditcard(post, options[:credit_card]) add_address(post, options) add_customer_data(post, options) add_credit_data(post, reference) commit('credit', money, post) end def credit(money, reference, options = {}) deprecated CREDIT_DEPRECATION_MESSAGE refund(money, reference, options) end def void(money, creditcard, options = {}) post = FirstPayPostData.new add_creditcard(post, creditcard) add_void_data(post, options) add_invoice(post, options) add_customer_data(post, options) commit('void', money, post) end private def add_customer_data(post, options) post[:cardip] = options[:ip] post[:email] = options[:email] end def add_address(post, options) if billing_address = options[:billing_address] || options[:address] post[:addr] = billing_address[:address1].to_s + ' ' + billing_address[:address2].to_s post[:city] = billing_address[:city] post[:state] = billing_address[:state] post[:zip] = billing_address[:zip] post[:country] = billing_address[:country] end end def add_invoice(post, options) post[:trackid] = rand(Time.now.to_i) end def add_creditcard(post, creditcard) post[:member] = creditcard.first_name.to_s + " " + creditcard.last_name.to_s post[:card] = creditcard.number post[:exp] = expdate(creditcard) end def expdate(credit_card) year = sprintf("%.4i", credit_card.year) month = sprintf("%.2i", credit_card.month) "#{month}#{year[-2..-1]}" end def add_credit_data(post, transaction_id) post[:transid] = transaction_id end def add_void_data(post, options) post[:transid] = options[:transactionid] end def commit(action, money, post) response = parse( ssl_post(test? ? TEST_URL : LIVE_URL, post_data(action, post, money)) ) Response.new(response[:response] == 'CAPTURED', response[:message], response, :test => test?, :authorization => response[:authorization], :avs_result => { :code => response[:avsresponse] }, :cvv_result => response[:cvvresponse]) end def parse(body) response = {} # check for an error first if body.include?('!ERROR!') response[:response] = 'ERROR' response[:message] = error_message_from(body) else # a capture / not captured response will be : delimited split = body.split(':') response[:response] = split[0] # FirstPay docs are worthless. turns out the transactionid is required for credits # so we need to store that in authorization, not the actual auth. if response[:response] == 'CAPTURED' response[:message] = 'CAPTURED' response[:authorization] = split[9] # actually the transactionid response[:auth] = split[1] response[:avsresponse] = split[3] response[:cvvresponse] = split[17] else # NOT CAPTURED response response[:message] = split[1] response[:transactionid] = split[9] end end return response end def error_message_from(response) # error messages use this format - '!ERROR! 704-MISSING BASIC DATA TYPE:card, exp, zip, addr, member, amount\n' response.split("! ")[1].chomp end def post_data(action, post, money) post[:vid] = @options[:login] post[:password] = @options[:password] post[:action] = ACTIONS[action] post[:amount] = amount(money) return post.to_post_data end end end end