require 'net/http' require 'net/https' require 'digest/md5' require 'active_merchant/billing/response' module ActiveMerchant #:nodoc: module Billing #:nodoc: # # == Description # The Gateway class is the base class for all ActiveMerchant gateway implementations. # # The list of gateway functions that concrete gateway classes can and should implement include # the following: # # * purchase(money, creditcard, options = {}) # * authorize(money, creditcard, options = {}) # * capture(money, authorization, options = {}) # * void(identification, options = {}) # * credit(money, identification, options = {}) # # == Setting Up Your Gateway # Aside from the obvious authorization parameters (login and password), you can set up your # gateway using numerous options. Be sure to reference your gateway of choice's documentation # before overriding it's default values that may be defined. # # * Gateway.default_currency: sets the default currency if none is provided. See # http://en.wikipedia.org/wiki/ISO_4217#Active_codes for active currency codes. # # * Gateway.supported_countries: sets the countries of _merchants_ the gateway supports. # # * Gateway.supported_cardtypes: sets the card types supported by the gateway. # # * Gateway.homepage_url: sets the URL at which the gateway may be found. # # * Gateway.display_name: sets the name of the gateway for display purposes, such as generating documentation. # # * Gateway.application_id: This is the application making calls to the gateway. This # is useful for things like the Paypal build notation (BN) id fields. # # * Gateway.money_format: this attribute may be set to :dollars or # :cents. Use this to set the expected money format you'll be inputting. # # # Gateway.money_format = :dollars # => 12.50 # Gateway.money_format = :cents # => 1250 # # # == Testing Your Code # There are two kinds of tests performed with your code: local and remote. # # === Local Tests # Before running any remote tests, it's best to ensure that your code covers the basics # locally. Local tests run on your own machine and will run faster than remote tests. # # To run a local test, first ensure that your gateway is in test mode: # # ActiveMerchant::Base.mode = :test # # (See ActiveMerchant::Base for more details.) This is often best set in your test's +setup+ # or +teardown+ methods, if you are using Test::Unit. # # The next step is to use one of three test credit card numbers: # # 1:: Result will be successful # 2:: Result will be a failure # 3:: Result will raise a miscellaneous error # # For examples of test requests, please see your gateway of interest's unit test code. # # === Remote Tests # Remote tests aren't mandatory, but it's not a bad idea to write them to ensure everything # works as expected. You'll first need authorization parameters from the gateway you'll be # working with. Once you have these values you'll be able to use ActiveMerchant to run test # requests. # # As with local tests, first ensure that you are in test mode: # # ActiveMerchant::Base.mode = :test # # (See ActiveMerchant::Base for more details.) # # Test requests may then be made using appropriate parameters provided by your gateway of # choice. For instance, the Moneris gateway provides a test MasterCard and Visa number that # one may use to process test purchases and authorization requests. # # Given that these remote tests will take longer to run than local tests, it is recommended # that you comment them out, or disable them when not required. class Gateway include PostsData include RequiresParameters include CreditCardFormatting ## Constants DEBIT_CARDS = [ :switch, :solo ] ## Attributes # The format of the amounts used by the gateway # :dollars => '12.50' # :cents => '1250' class_inheritable_accessor :money_format self.money_format = :dollars # The default currency for the transactions if no currency is provided class_inheritable_accessor :default_currency # The countries of merchants the gateway supports class_inheritable_accessor :supported_countries self.supported_countries = [] # The supported card types for the gateway class_inheritable_accessor :supported_cardtypes self.supported_cardtypes = [] class_inheritable_accessor :homepage_url class_inheritable_accessor :display_name # The application making the calls to the gateway # Useful for things like the PayPal build notation (BN) id fields class_inheritable_accessor :application_id self.application_id = 'ActiveMerchant' attr_reader :options # Use this method to check if your gateway of interest supports a credit card of some type def self.supports?(card_type) supported_cardtypes.include?(card_type.to_sym) end ## Instance Methods # Initialize a new gateway. # # See the documentation for the gateway you will be using to make sure there are no other # required options. def initialize(options = {}) end # Are we running in test mode? def test? Base.gateway_mode == :test end private # :nodoc: all def name self.class.name.scan(/\:\:(\w+)Gateway/).flatten.first end # This is used to check if our credit card number implies that we are seeking a test # Response. Of course, this returns false if we are not in test mode. # # Recognized values: # 1:: Result will be successful # 2:: Result will be a failure # 3:: Result will raise a miscellaneous error # # All other values will not be recognized. #-- # TODO Refactor this method. It's kind of on the ugly side of things. def test_result_from_cc_number(card_number) return false unless test? case card_number.to_s when '1', 'success' Response.new(true, 'Successful test mode response', {:receiptid => '#0001'}, :test => true, :authorization => '5555') when '2', 'failure' Response.new(false, 'Failed test mode response', {:receiptid => '#0001'}, :test => true) when '3', 'error' raise Error, 'big bad exception' else false end end # Return a String with the amount in the appropriate format #-- # TODO Refactor this method. It's a tad on the ugly side. def amount(money) return nil if money.nil? cents = money.respond_to?(:cents) ? money.cents : money if money.is_a?(String) or cents.to_i < 0 raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' end if self.money_format == :cents cents.to_s else sprintf("%.2f", cents.to_f / 100) end end # Ascertains the currency to be used on the money supplied. def currency(money) money.respond_to?(:currency) ? money.currency : self.default_currency end def requires_start_date_or_issue_number?(credit_card) return false if credit_card.type.blank? DEBIT_CARDS.include?(credit_card.type.to_sym) end def generate_unique_id md5 = Digest::MD5.new now = Time.now md5 << now.to_s md5 << String(now.usec) md5 << String(rand(0)) md5 << String($$) md5 << self.class.name md5.hexdigest end end end end