module ActiveMerchant #:nodoc: module Billing #:nodoc: class PayHubGateway < Gateway self.live_url = 'https://checkout.payhub.com/transaction/api' self.supported_countries = ['US'] self.default_currency = 'USD' self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.payhub.com/' self.display_name = 'PayHub' CVV_CODE_TRANSLATOR = { 'M' => 'CVV matches', 'N' => 'CVV does not match', 'P' => 'CVV not processed', 'S' => 'CVV should have been present', 'U' => 'CVV request unable to be processed by issuer' } AVS_CODE_TRANSLATOR = { '0' => 'Approved, Address verification was not requested.', 'A' => 'Approved, Address matches only.', 'B' => 'Address Match. Street Address math for international transaction Postal Code not verified because of incompatible formats (Acquirer sent both street address and Postal Code)', 'C' => 'Serv Unavailable. Street address and Postal Code not verified for international transaction because of incompatible formats (Acquirer sent both street and Postal Code).', 'D' => 'Exact Match, Street Address and Postal Code match for international transaction.', 'F' => 'Exact Match, Street Address and Postal Code match. Applies to UK only.', 'G' => 'Ver Unavailable, Non-U.S. Issuer does not participate.', 'I' => 'Ver Unavailable, Address information not verified for international transaction', 'M' => 'Exact Match, Street Address and Postal Code match for international transaction', 'N' => 'No - Address and ZIP Code does not match', 'P' => 'Zip Match, Postal Codes match for international transaction Street address not verified because of incompatible formats (Acquirer sent both street address and Postal Code).', 'R' => 'Retry - Issuer system unavailable', 'S' => 'Serv Unavailable, Service not supported', 'U' => 'Ver Unavailable, Address unavailable.', 'W' => 'ZIP match - Nine character numeric ZIP match only.', 'X' => 'Exact match, Address and nine-character ZIP match.', 'Y' => 'Exact Match, Address and five character ZIP match.', 'Z' => 'Zip Match, Five character numeric ZIP match only.', '1' => 'Cardholder name and ZIP match AMEX only.', '2' => 'Cardholder name, address, and ZIP match AMEX only.', '3' => 'Cardholder name and address match AMEX only.', '4' => 'Cardholder name match AMEX only.', '5' => 'Cardholder name incorrect, ZIP match AMEX only.', '6' => 'Cardholder name incorrect, address and ZIP match AMEX only.', '7' => 'Cardholder name incorrect, address match AMEX only.', '8' => 'Cardholder, all do not match AMEX only.' } STANDARD_ERROR_CODE_MAPPING = { '14' => STANDARD_ERROR_CODE[:invalid_number], '80' => STANDARD_ERROR_CODE[:invalid_expiry_date], '82' => STANDARD_ERROR_CODE[:invalid_cvc], '54' => STANDARD_ERROR_CODE[:expired_card], '51' => STANDARD_ERROR_CODE[:card_declined], '05' => STANDARD_ERROR_CODE[:card_declined], '61' => STANDARD_ERROR_CODE[:card_declined], '62' => STANDARD_ERROR_CODE[:card_declined], '65' => STANDARD_ERROR_CODE[:card_declined], '93' => STANDARD_ERROR_CODE[:card_declined], '01' => STANDARD_ERROR_CODE[:call_issuer], '02' => STANDARD_ERROR_CODE[:call_issuer], '04' => STANDARD_ERROR_CODE[:pickup_card], '07' => STANDARD_ERROR_CODE[:pickup_card], '41' => STANDARD_ERROR_CODE[:pickup_card], '43' => STANDARD_ERROR_CODE[:pickup_card] } def initialize(options = {}) requires!(options, :orgid, :username, :password, :tid) super end def authorize(amount, creditcard, options = {}) post = setup_post('auth') add_creditcard(post, creditcard) add_amount(post, amount) add_address(post, (options[:address] || options[:billing_address])) add_customer_data(post, options) commit(post) end def purchase(amount, creditcard, options = {}) post = setup_post('sale') add_creditcard(post, creditcard) add_amount(post, amount) add_address(post, (options[:address] || options[:billing_address])) add_customer_data(post, options) commit(post) end def refund(amount, trans_id, options = {}) # Attempt a void in case the transaction is unsettled post = setup_post('void') add_reference(post, trans_id) response = commit(post) return response if response.success? post = setup_post('refund') add_reference(post, trans_id) commit(post) end def capture(amount, trans_id, options = {}) post = setup_post('capture') add_reference(post, trans_id) add_amount(post, amount) commit(post) end # No void, as PayHub's void does not work on authorizations def verify(creditcard, options = {}) authorize(100, creditcard, options) end private def setup_post(action) post = {} post[:orgid] = @options[:orgid] post[:tid] = @options[:tid] post[:username] = @options[:username] post[:password] = @options[:password] post[:mode] = (test? ? 'demo' : 'live') post[:trans_type] = action post end def add_reference(post, trans_id) post[:trans_id] = trans_id end def add_customer_data(post, options = {}) post[:first_name] = options[:first_name] post[:last_name] = options[:last_name] post[:phone] = options[:phone] post[:email] = options[:email] end def add_address(post, address) return unless address post[:address1] = address[:address1] post[:address2] = address[:address2] post[:zip] = address[:zip] post[:state] = address[:state] post[:city] = address[:city] end def add_amount(post, amount) post[:amount] = amount(amount) end def add_creditcard(post, creditcard) post[:cc] = creditcard.number post[:month] = creditcard.month.to_s post[:year] = creditcard.year.to_s post[:cvv] = creditcard.verification_value end def parse(body) JSON.parse(body) end def commit(post) success = false begin raw_response = ssl_post(live_url, post.to_json, { 'Content-Type' => 'application/json' }) response = parse(raw_response) success = (response['RESPONSE_CODE'] == '00') rescue ResponseError => e raw_response = e.response.body response = response_error(raw_response) rescue JSON::ParserError response = json_error(raw_response) end Response.new(success, response_message(response), response, test: test?, avs_result: { code: response['AVS_RESULT_CODE'] }, cvv_result: response['VERIFICATION_RESULT_CODE'], error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['RESPONSE_CODE']]), authorization: response['TRANSACTION_ID']) end def response_error(raw_response) parse(raw_response) rescue JSON::ParserError json_error(raw_response) end def json_error(raw_response) { error_message: 'Invalid response received from the Payhub API. Please contact wecare@payhub.com if you continue to receive this message.' \ " (The raw response returned by the API was #{raw_response.inspect})" } end def response_message(response) (response['RESPONSE_TEXT'] || response['RESPONSE_CODE'] || response[:error_message]) end end end end