lib/active_merchant/billing/credit_card.rb in activemerchant-1.43.3 vs lib/active_merchant/billing/credit_card.rb in activemerchant-1.44.0
- old
+ new
@@ -1,8 +1,8 @@
require 'time'
require 'date'
-require 'active_merchant/billing/expiry_date'
+require "active_merchant/billing/model"
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# A +CreditCard+ object represents a physical credit card, and is capable of validating the various
# data associated with these.
@@ -41,34 +41,37 @@
# :year => '2010',
# :brand => 'visa',
# :number => '4242424242424242'
# )
#
- # cc.valid? # => true
+ # cc.validate # => {}
# cc.display_number # => XXXX-XXXX-XXXX-4242
#
- class CreditCard
+ class CreditCard < Model
include CreditCardMethods
- include Validateable
cattr_accessor :require_verification_value
self.require_verification_value = true
# Returns or sets the credit card number.
#
# @return [String]
- attr_accessor :number
+ attr_reader :number
+ def number=(value)
+ @number = (empty?(value) ? value : value.to_s.gsub(/[^\d]/, ""))
+ end
+
# Returns or sets the expiry month for the card.
#
# @return [Integer]
- attr_accessor :month
+ attr_reader :month
# Returns or sets the expiry year for the card.
#
# @return [Integer]
- attr_accessor :year
+ attr_reader :year
# Returns or sets the credit card brand.
#
# Valid card types are
#
@@ -86,12 +89,23 @@
# * +'laser'+
#
# Or, if you wish to test your implementation, +'bogus'+.
#
# @return (String) the credit card brand
- attr_accessor :brand
+ def brand
+ if !defined?(@brand) || empty?(@brand)
+ self.class.brand?(number)
+ else
+ @brand
+ end
+ end
+ def brand=(value)
+ value = value && value.to_s.dup
+ @brand = (value.respond_to?(:downcase) ? value.downcase : value)
+ end
+
# Returns or sets the first name of the card holder.
#
# @return [String]
attr_accessor :first_name
@@ -99,11 +113,12 @@
#
# @return [String]
attr_accessor :last_name
# Required for Switch / Solo cards
- attr_accessor :start_month, :start_year, :issue_number
+ attr_reader :start_month, :start_year
+ attr_accessor :issue_number
# Returns or sets the card verification value.
#
# This attribute is optional but recommended. The verification value is
# a {card security code}[http://en.wikipedia.org/wiki/Card_security_code]. If provided,
@@ -116,16 +131,16 @@
#
# @return [String]
attr_accessor :track_data
def type
- self.class.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
+ ActiveMerchant.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
brand
end
def type=(value)
- self.class.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
+ ActiveMerchant.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
self.brand = value
end
# Provides proxy access to an expiry date object
#
@@ -146,33 +161,46 @@
first_name? || last_name?
end
# Returns whether the +first_name+ attribute has been set.
def first_name?
- @first_name.present?
+ first_name.present?
end
# Returns whether the +last_name+ attribute has been set.
def last_name?
- @last_name.present?
+ last_name.present?
end
# Returns the full name of the card holder.
#
# @return [String] the full name of the card holder
def name
- [@first_name, @last_name].compact.join(' ')
+ [first_name, last_name].compact.join(' ')
end
def name=(full_name)
names = full_name.split
self.last_name = names.pop
self.first_name = names.join(" ")
end
+ %w(month year start_month start_year).each do |m|
+ class_eval %(
+ def #{m}=(v)
+ @#{m} = case v
+ when "", nil, 0
+ nil
+ else
+ v.to_i
+ end
+ end
+ )
+ end
+
def verification_value?
- !@verification_value.blank?
+ !verification_value.blank?
end
# Returns a display-friendly version of the card number.
#
# All but the last 4 numbers are replaced with an "X", and hyphens are
@@ -197,86 +225,127 @@
# Validates the credit card details.
#
# Any validation errors are added to the {#errors} attribute.
def validate
- validate_essential_attributes
+ errors = validate_essential_attributes + validate_verification_value
# Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used
- return if brand == 'bogus'
+ return errors_hash(errors) if brand == 'bogus'
- validate_card_brand
- validate_card_number
- validate_verification_value
- validate_switch_or_solo_attributes
+ errors_hash(
+ errors +
+ validate_card_brand_and_number +
+ validate_switch_or_solo_attributes
+ )
end
def self.requires_verification_value?
require_verification_value
end
private
- def before_validate #:nodoc:
- self.month = month.to_i
- self.year = year.to_i
- self.start_month = start_month.to_i unless start_month.nil?
- self.start_year = start_year.to_i unless start_year.nil?
- self.number = number.to_s.gsub(/[^\d]/, "")
- self.brand.downcase! if brand.respond_to?(:downcase)
- self.brand = self.class.brand?(number) if brand.blank?
+ def validate_essential_attributes #:nodoc:
+ errors = []
+
+ errors << [:first_name, "cannot be empty"] if first_name.blank?
+ errors << [:last_name, "cannot be empty"] if last_name.blank?
+
+ if(empty?(month) || empty?(year))
+ errors << [:month, "is required"] if empty?(month)
+ errors << [:year, "is required"] if empty?(year)
+ else
+ errors << [:month, "is not a valid month"] if !valid_month?(month)
+
+ if expired?
+ errors << [:year, "expired"]
+ else
+ errors << [:year, "is not a valid year"] if !valid_expiry_year?(year)
+ end
+ end
+
+ errors
end
- def validate_card_number #:nodoc:
- if number.blank?
- errors.add :number, "is required"
+ def validate_card_brand_and_number #:nodoc:
+ errors = []
+
+ if !empty?(brand)
+ errors << [:brand, "is invalid"] if !CreditCard.card_companies.keys.include?(brand)
+ end
+
+ if empty?(number)
+ errors << [:number, "is required"]
elsif !CreditCard.valid_number?(number)
- errors.add :number, "is not a valid credit card number"
+ errors << [:number, "is not a valid credit card number"]
end
- unless errors.on(:number) || errors.on(:brand)
- errors.add :brand, "does not match the card number" unless CreditCard.matching_brand?(number, brand)
+ if errors.empty?
+ errors << [:brand, "does not match the card number"] if !CreditCard.matching_brand?(number, brand)
end
- end
- def validate_card_brand #:nodoc:
- errors.add :brand, "is required" if brand.blank? && number.present?
- errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand)
+ errors
end
- alias_method :validate_card_type, :validate_card_brand
+ def validate_verification_value #:nodoc:
+ errors = []
- def validate_essential_attributes #:nodoc:
- errors.add :first_name, "cannot be empty" if @first_name.blank?
- errors.add :last_name, "cannot be empty" if @last_name.blank?
-
- if @month.to_i.zero? || @year.to_i.zero?
- errors.add :month, "is required" if @month.to_i.zero?
- errors.add :year, "is required" if @year.to_i.zero?
- else
- errors.add :month, "is not a valid month" unless valid_month?(@month)
- errors.add :year, "expired" if expired?
- errors.add :year, "is not a valid year" unless expired? || valid_expiry_year?(@year)
+ if verification_value?
+ unless valid_card_verification_value?(verification_value, brand)
+ errors << [:verification_value, "should be #{card_verification_value_length(brand)} digits"]
+ end
+ elsif CreditCard.requires_verification_value?
+ errors << [:verification_value, "is required"]
end
+ errors
end
def validate_switch_or_solo_attributes #:nodoc:
+ errors = []
+
if %w[switch solo].include?(brand)
- unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number)
- if @issue_number.blank?
- errors.add :start_month, "is invalid" unless valid_month?(@start_month)
- errors.add :start_year, "is invalid" unless valid_start_year?(@start_year)
- errors.add :issue_number, "cannot be empty"
+ valid_start_month = valid_month?(start_month)
+ valid_start_year = valid_start_year?(start_year)
+
+ if((!valid_start_month || !valid_start_year) && !valid_issue_number?(issue_number))
+ if empty?(issue_number)
+ errors << [:issue_number, "cannot be empty"]
+ errors << [:start_month, "is invalid"] if !valid_start_month
+ errors << [:start_year, "is invalid"] if !valid_start_year
else
- errors.add :issue_number, "is invalid" unless valid_issue_number?(@issue_number)
+ errors << [:issue_number, "is invalid"] if !valid_issue_number?(issue_number)
end
end
end
+
+ errors
end
- def validate_verification_value #:nodoc:
- if CreditCard.requires_verification_value?
- errors.add :verification_value, "is required" unless verification_value?
+ class ExpiryDate #:nodoc:
+ attr_reader :month, :year
+ def initialize(month, year)
+ @month = month.to_i
+ @year = year.to_i
+ end
+
+ def expired? #:nodoc:
+ Time.now.utc > expiration
+ end
+
+ def expiration #:nodoc:
+ begin
+ Time.utc(year, month, month_days, 23, 59, 59)
+ rescue ArgumentError
+ Time.at(0).utc
+ end
+ end
+
+ private
+ def month_days
+ mdays = [nil,31,28,31,30,31,30,31,31,30,31,30,31]
+ mdays[2] = 29 if Date.leap?(year)
+ mdays[month]
end
end
end
end
end