lib/active_merchant/billing/gateways/trust_commerce.rb in activemerchant-1.2.1 vs lib/active_merchant/billing/gateways/trust_commerce.rb in activemerchant-1.3.0

- old
+ new

@@ -1,18 +1,13 @@ begin require 'tclink' rescue LoadError - # Ignore, but we will fail hard if someone actually tries to use this gateway + # Falls back to an SSL post to TrustCommerce end module ActiveMerchant #:nodoc: module Billing #:nodoc: - - # To get started using TrustCommerce with active_merchant, download the tclink library from http://www.trustcommerce.com/tclink.html, - # following the instructions available there to get it working on your system. Once it is installed, you should be able to make sure - # that it is visible to your ruby install by opening irb and typing "require 'tclink'", which should return "true". - # # TO USE: # First, make sure you have everything setup correctly and all of your dependencies in place with: # # require 'rubygems' # require 'active_merchant' @@ -54,14 +49,26 @@ # # 3) Retrieve and store the unique transaction ID returned by Trust Commerece, for use in referencing the transaction in the future. # # response.params["transid"] # + # For higher performance and failover with the TrustCommerceGateway you can install the TCLink library from http://www.trustcommerce.com/tclink.html. + # Follow the instructions available there to get it working on your system. ActiveMerchant will automatically use tclink if available. + # + # The TCLink library has the following added benefits: + # * Good transaction times. Transaction duration under 1.2 seconds are common. + # * Fail-over to geographically distributed servers for extreme reliability + # + # Once it is installed, you should be able to make sure + # that it is visible to your ruby install by opening irb and typing "require 'tclink'", which should return "true". + # # This should be enough to get you started with Trust Commerce and active_merchant. For further information, review the methods # below and the rest of active_merchant's documentation, as well as Trust Commerce's user and developer documentation. class TrustCommerceGateway < Gateway + URL = 'https://vault.trustcommerce.com/trans/' + SUCCESS_TYPES = ["approved", "accepted"] DECLINE_CODES = { "decline" => "The credit card was declined", "avs" => "AVS failed; the address entered does not match the billing address on file at the bank", @@ -92,21 +99,20 @@ "dnsfailure" => "The TCLink software was unable to resolve DNS hostnames", "linkfailure" => "The connection was established, but was severed before the transaction could complete", "failtoprocess" => "The bank servers are offline and unable to authorize transactions" } - # URL - attr_reader :url - attr_reader :response - attr_reader :options - self.money_format = :cents self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club, :jcb] self.supported_countries = ['US'] self.homepage_url = 'http://www.trustcommerce.com/' self.display_name = 'TrustCommerce' + def self.tclink? + defined?(TCLink) + end + # Creates a new TrustCommerceGateway # # The gateway requires that a valid login and password be passed # in the +options+ hash. # @@ -124,12 +130,16 @@ @options = options super end + def tclink? + self.class.tclink? + end + def test? - @options[:test] || Base.gateway_mode == :test + @options[:test] || super end # authorize() is the first half of the preauth(authorize)/postauth(capture) model. The TC API docs call this # preauth, we preserve active_merchant's nomenclature of authorize() for consistency with the rest of the library. This # method simply checks to make sure funds are available for a transaction, and returns a transid that can be used later to @@ -138,32 +148,34 @@ def authorize(money, creditcard_or_billing_id, options = {}) parameters = { :amount => amount(money), } + add_order_id(parameters, options) + add_customer_data(parameters, options) add_payment_source(parameters, creditcard_or_billing_id) - add_address(parameters, options) + add_addresses(parameters, options) commit('preauth', parameters) end # purchase() is a simple sale. This is one of the most common types of transactions, and is extremely simple. All that you need # to process a purchase are an amount in cents or a money object and a creditcard object or billingid string. - def purchase(money, creditcard_or_billing_id, options = {}) parameters = { :amount => amount(money), } + add_order_id(parameters, options) + add_customer_data(parameters, options) add_payment_source(parameters, creditcard_or_billing_id) - add_address(parameters, options) + add_addresses(parameters, options) commit('sale', parameters) end # capture() is the second half of the preauth(authorize)/postauth(capture) model. The TC API docs call this # postauth, we preserve active_merchant's nomenclature of capture() for consistency with the rest of the library. To process # a postauthorization with TC, you need an amount in cents or a money object, and a TC transid. - def capture(money, authorization, options = {}) parameters = { :amount => amount(money), :transid => authorization, } @@ -171,20 +183,41 @@ commit('postauth', parameters) end # credit() allows you to return money to a card that was previously billed. You need to supply the amount, in cents or a money object, # that you want to refund, and a TC transid for the transaction that you are refunding. - def credit(money, identification, options = {}) parameters = { :amount => amount(money), :transid => identification } commit('credit', parameters) end + # void() clears an existing authorization and releases the reserved fund + # s back to the cardholder. The TC API refers to this transaction as a + # reversal. After voiding, you will no longer be able to capture funds + # from this authorization. TrustCommerce seems to always return a status + # of "accepted" even if the transid you are trying to deauthorize has + # already been captured. Note: Your account needs to be configured by + # TrustCommerce to allow for reversal transactions before you can use this + # method. + # + # NOTE: AMEX preauth's cannot be reversed. If you want to clear it more + # quickly than the automatic expiration (7-10 days), you will have to + # capture it and then immediately issue a credit for the same amount + # which should clear the customers credit card with 48 hours according to + # TC. + def void(authorization, options = {}) + parameters = { + :transid => authorization, + } + + commit('reversal', parameters) + end + # recurring() a TrustCommerce account that is activated for Citatdel, TrustCommerce's # hosted customer billing info database. # # Recurring billing uses the same TC action as a plain-vanilla 'store', but we have a separate method for clarity. It can be called # like store, with the addition of a required 'periodicity' parameter: @@ -192,11 +225,10 @@ # The parameter :periodicity should be specified as either :bimonthly, :monthly, :biweekly, :weekly, :yearly or :daily # # gateway.recurring(tendollar, creditcard, :periodicity => :weekly) # # You can optionally specify how long you want payments to continue using 'payments' - def recurring(money, creditcard, options = {}) requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily] ) cycle = case options[:periodicity] when :monthly @@ -235,11 +267,11 @@ :verify => options[:verify] || 'y', :billingid => options[:billingid] || nil, } add_creditcard(parameters, creditcard) - add_address(parameters, options) + add_addresses(parameters, options) commit('store', parameters) end # To unstore a creditcard stored in Citadel using store() or recurring(), all that is required is the billing id. When you run # unstore() the information will be removed and a Response object will be returned indicating the success of the action. @@ -273,26 +305,45 @@ params[:cc] = creditcard.number params[:exp] = expdate(creditcard) params[:cvv] = creditcard.verification_value if creditcard.verification_value? end + def add_order_id(params, options) + params[:ticket] = options[:order_id] unless options[:order_id].blank? + end + def add_billing_id(params, billingid) params[:billingid] = billingid end - def add_address(params, options) + def add_customer_data(params, options) + params[:email] = options[:email] unless options[:email].blank? + params[:ip] = options[:ip] unless options[:ip].blank? + end + + def add_addresses(params, options) address = options[:billing_address] || options[:address] if address params[:address1] = address[:address1] unless address[:address1].blank? params[:address2] = address[:address2] unless address[:address2].blank? params[:city] = address[:city] unless address[:city].blank? params[:state] = address[:state] unless address[:state].blank? params[:zip] = address[:zip] unless address[:zip].blank? params[:country] = address[:country] unless address[:country].blank? params[:avs] = 'n' - end + end + + if shipping_address = options[:shipping_address] + params[:shipto_name] = shipping_address[:name] unless shipping_address[:name].blank? + params[:shipto_address1] = shipping_address[:address1] unless shipping_address[:address1].blank? + params[:shipto_address2] = shipping_address[:address2] unless shipping_address[:address2].blank? + params[:shipto_city] = shipping_address[:city] unless shipping_address[:city].blank? + params[:shipto_state] = shipping_address[:state] unless shipping_address[:state].blank? + params[:shipto_zip] = shipping_address[:zip] unless shipping_address[:zip].blank? + params[:shipto_country] = shipping_address[:country] unless shipping_address[:country].blank? + end end def clean_and_stringify_params(parameters) # TCLink wants us to send a hash with string keys, and activemerchant pushes everything around with # symbol keys. Before sending our input to TCLink, we convert all our keys to strings and dump the symbol keys. @@ -302,38 +353,48 @@ parameters[key.to_s] = parameters[key] end parameters.delete(key) end end + + def post_data(parameters) + parameters.collect { |key, value| "#{key}=#{ CGI.escape(value.to_s)}" }.join("&") + end def commit(action, parameters) - test = test? || parameters[:test_request] parameters[:custid] = @options[:login] parameters[:password] = @options[:password] - parameters[:demo] = test ? 'y' : 'n' + parameters[:demo] = test? ? 'y' : 'n' parameters[:action] = action - if result = test_result_from_cc_number(parameters[:cc]) - return result + clean_and_stringify_params(parameters) + + data = if tclink? + TCLink.send(parameters) + else + parse( ssl_post(URL, post_data(parameters)) ) end - begin - clean_and_stringify_params(parameters) + # to be considered successful, transaction status must be either "approved" or "accepted" + success = SUCCESS_TYPES.include?(data["status"]) + message = message_from(data) + Response.new(success, message, data, + :test => test?, + :authorization => data["transid"], + :cvv_result => data["cvv"], + :avs_result => { :code => data["avs"] } + ) + end + + def parse(body) + results = {} - data = TCLink.send(parameters) - # to be considered successful, transaction status must be either "approved" or "accepted" - success = SUCCESS_TYPES.include?(data["status"]) - message = message_from(data) - - Response.new(success, message, data, :test => test, :authorization => data["transid"] ) - rescue NameError => e - if e.message =~ /constant TCLink/ - raise 'Trust Commerce requires "tclink" library from http://www.trustcommerce.com/tclink.html' - else - raise - end + body.split(/\n/).each do |pair| + key,val = pair.split(/=/) + results[key] = val end + results end def message_from(data) status = case data["status"] when "decline" \ No newline at end of file