lib/active_merchant/billing/gateway.rb in activemerchant-1.1.0 vs lib/active_merchant/billing/gateway.rb in activemerchant-1.2.0

- old
+ new

@@ -1,76 +1,170 @@ require 'net/http' require 'net/https' +require 'digest/md5' require 'active_merchant/billing/response' module ActiveMerchant #:nodoc: module Billing #:nodoc: - # 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: # - # === Core operations supported by most gateways - # * purchase(money, creditcard, options = {}) - # * authorize(money, creditcard, options = {}) - # * capture(money, authorization, options = {}) - # * void(identification, options = {}) - # * credit(money, identification, options = {}) + # == 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: + # + # * <tt>purchase(money, creditcard, options = {})</tt> + # * <tt>authorize(money, creditcard, options = {})</tt> + # * <tt>capture(money, authorization, options = {})</tt> + # * <tt>void(identification, options = {})</tt> + # * <tt>credit(money, identification, options = {})</tt> + # + # == 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. + # + # * <tt>Gateway.default_currency</tt>: sets the default currency if none is provided. See + # http://en.wikipedia.org/wiki/ISO_4217#Active_codes for active currency codes. + # + # * <tt>Gateway.supported_countries</tt>: sets the countries of _merchants_ the gateway supports. + # + # * <tt>Gateway.supported_cardtypes</tt>: sets the card types supported by the gateway. + # + # * <tt>Gateway.homepage_url</tt>: sets the URL at which the gateway may be found. + # + # * <tt>Gateway.display_name</tt>: sets the name of the gateway for display purposes, such as generating documentation. + # + # * <tt>Gateway.application_id</tt>: This is the application making calls to the gateway. This + # is useful for things like the Paypal build notation (BN) id fields. + # + # * <tt>Gateway.money_format</tt>: this attribute may be set to <tt>:dollars</tt> or + # <tt>:cents</tt>. 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: + # + # <tt>1</tt>:: Result will be successful + # <tt>2</tt>:: Result will be a failure + # <tt>3</tt>:: 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 - # Return the matching gateway for the provider - # * <tt>bogus</tt>: BogusGateway - Does nothing ( for testing) - # * <tt>moneris</tt>: MonerisGateway - # * <tt>authorize_net</tt>: AuthorizeNetGateway - # * <tt>trust_commerce</tt>: TrustCommerceGateway - # - # ActiveMerchant::Base.gateway('moneris').new - def self.gateway(name) - ActiveMerchant::Billing.const_get("#{name.to_s.downcase}_gateway".camelize) - end - - # Does this gateway support credit cards of the passed type? - def self.supports?(type) - supported_cardtypes.include?(type.intern) - end - - # Get a list of supported credit card types for this gateway - def self.supported_cardtypes - [] - end - + # 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 - # Initialize a new gateway + + # 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 = {}) - @ssl_strict = options[:ssl_strict] || false + # 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 + private # :nodoc: all + def name self.class.name.scan(/\:\:(\w+)Gateway/).flatten.first end - def test_result_from_cc_number(number) + # 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: + # <tt>1</tt>:: Result will be successful + # <tt>2</tt>:: Result will be a failure + # <tt>3</tt>:: 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 number.to_s + 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' @@ -78,24 +172,46 @@ else false end end - # Return a string with the amount in the appropriate format + # 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 - case self.money_format - when :cents + if self.money_format == :cents cents.to_s else - sprintf("%.2f", cents.to_f/100) + sprintf("%.2f", cents.to_f / 100) end - 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