CreditCardValidations

Gem Version License Coverage

Gem adds a validator to check whether a given number actually falls within the ranges of possible numbers prior to performing verification — CreditCardValidations verifies that the credit card number provided is well-formed.

More info about card BIN numbers: http://en.wikipedia.org/wiki/Bank_card_number

Installation

Add this line to your application's Gemfile:

$ gem 'credit_card_validations'

And then execute:

$ bundle

Or install it yourself as:

$ gem install credit_card_validations

Default brands

These brands are detected out of the box. They are the international majors that most acquirers, gateways, and payment forms care about:

Name Key
American Express :amex
China UnionPay :unionpay
Diners Club :diners
Discover :discover
JCB :jcb
Maestro :maestro
MasterCard :mastercard
Visa :visa

Opt-in plugins

Everything else is detected only when its plugin is explicitly required. Plugins add no startup cost, no API surface, and no chance of misdetection to apps that don't accept the brand.

Active regional and specialty networks

Name Key
Cabal :cabal
Carnet :carnet
Cartes Bancaires :cartes_bancaires
Dankort :dankort
DinaCard :dinacard
Elo :elo
Girocard :girocard
Hiper :hiper
Hipercard :hipercard
Humo :humocard
Mada :mada
MIR :mir
Naranja :naranja
RuPay :rupay
Troy :troy
UATP :uatp
Uzcard :uzcard
V Pay :vpay
Verve :verve
Voyager :voyager

Legacy / withdrawn networks

Name Key Status
Diners Club US :diners_us Merged into Discover for US routing in 2008
EnRoute :en_route Withdrawn 1989
Laser :laser Withdrawn 2014
Solo :solo Withdrawn 2011
Switch :switch Withdrawn 2002

Loading plugins

# in an initializer or before first use
require 'credit_card_validations/plugins/mir'
require 'credit_card_validations/plugins/elo'
require 'credit_card_validations/plugins/hipercard'
# ... whichever brands the app actually accepts

Migrating from v8.x → v9.0

Seven brands moved from the default brand set to opt-in plugins in v9.0. The auto-require shim keeps existing code working for one major version with a one-time deprecation warning per brand.

Brand Status Auto-loaded until
:dankort Active (Denmark) v10.0
:elo Active (Brazil) v10.0
:hipercard Active (Brazil) v10.0
:mir Active (Russia) v10.0
:rupay Active (India) v10.0
:solo Withdrawn 2011 v10.0
:switch Withdrawn 2002 v10.0

If your code references any of these brands by symbol, add the matching require to your initializer to silence the warning and survive v10:

# config/initializers/credit_card_validations.rb
require 'credit_card_validations/plugins/mir'
require 'credit_card_validations/plugins/elo'
# ...

When v10 lands, the auto-load disappears. Code that names these brands without a matching require will see them as unknown — Detector#brand returns nil, predicate methods (mir?, elo?, …) are not defined, and valid?(:mir) returns false.

Other breaking changes in v9.0

  • Hipercard cleaned up. Length changed from 19 to 16 (which is the issued length); legacy 637* prefixes that actually belong to Hiper were dropped. If you used Hipercard before, your detection now matches the brand's real spec.
  • Discover cleaned up. Diners-only prefixes (300-305, 3095, 36, 38, 39) were dropped from Discover. Diners cards now correctly detect as :diners instead of :discover. Apps that branched on :discover for routing should branch on [:diners, :discover].
  • Luhn.valid? is now strict. It accepts a digit-only string and returns false for nil, empty input, or any non-digit character. User-facing input handling moved into Detector#initialize, which strips whitespace and dashes before delegating.
  • Brand YAML is loaded via YAML.safe_load_file. Custom brand sources may need to declare permitted_classes: [Symbol] if they relied on extra Ruby objects.

Usage

String monkey patch

require 'credit_card_validations/string'
'5274 5763 9425 9961'.credit_card_brand                                #=> :mastercard
'5274 5763 9425 9961'.credit_card_brand_name                           #=> "MasterCard"
'5274 5763 9425 9961'.valid_credit_card_brand?(:mastercard, :visa)     #=> true
'5274 5763 9425 9961'.valid_credit_card_brand?(:amex)                  #=> false
'5274 5763 9425 9961'.valid_credit_card_brand?('MasterCard')           #=> true

ActiveModel validators

Restrict to a brand list:

class CreditCardModel
  attr_accessor :number
  include ActiveModel::Validations
  validates :number, credit_card_number: { brands: [:amex, :maestro] }
end

Accept any known brand:

validates :number, presence: true, credit_card_number: true

CVV validator

CVV against a brand pulled from another attribute (the PAN):

class Payment
  include ActiveModel::Validations
  attr_accessor :card_number, :cvv

  validates :card_number, credit_card_number: true
  validates :cvv,         credit_card_cvv: { brand_from: :card_number }
end

CVV against a literal brand:

validates :cvv, credit_card_cvv: { brand: :amex }

Expiration validator

A single string attribute (MM/YY, MM/YYYY, MMYY, ...):

validates :expiration, credit_card_expiration: true

Two separate fields (typical month + year dropdowns) — use the Expiration class in a validate block:

class Payment
  attr_accessor :exp_month, :exp_year

  validate do
    exp = CreditCardValidations::Expiration.new(exp_month, exp_year)
    errors.add(:exp_month, :invalid) unless exp.valid?
  end
end

CreditCardValidations::Card

A composite model wrapping Detector and Expiration behind a single ActiveModel-aware object:

card = CreditCardValidations::Card.new(
  number: '4111 1111 1111 1111',
  month: 12, year: 2027,
  verification_value: '123',
  name: 'John Smith'
)
card.valid?            # => true
card.brand             # => :visa
card.display_number    # => "************1111"
card.last_digits       # => "1111"
card.expired?          # => false
card.formatted_number  # => "4111 1111 1111 1111"

Using Detector directly

number   = '4111111111111111'
detector = CreditCardValidations::Detector.new(number)

detector.brand                          # => :visa
detector.visa?                          # => true
detector.valid?(:mastercard, :maestro)  # => false
detector.valid?(:visa, :mastercard)     # => true
detector.issuer_category                # => "Banking and financial"
detector.last4                          # => "1111"
detector.masked                         # => "************1111"
detector.formatted                      # => "4111 1111 1111 1111"
detector.possible_brands                # => [:visa]   (during live input)
detector.valid_cvv?('123')              # => true

Adding a custom brand at runtime

CreditCardValidations.add_brand(:voyager, { length: 15, prefixes: '86' })
CreditCardValidations::Detector.new('869926275400212').voyager?  # => true

Removing a brand at runtime

CreditCardValidations::Detector.delete_brand(:maestro)

Luhn check

CreditCardValidations::Detector.new(number).valid_luhn?
# or, on a clean digit string:
CreditCardValidations::Luhn.valid?(number)

Generating Luhn-valid test numbers

CreditCardValidations::Factory.random(:amex)
# => "348051773827666"
CreditCardValidations::Factory.random(:maestro)
# => "6010430241237266856"

Configuration

To override the default brand source, copy the bundled brands.yaml, edit it, and point the gem at it in a Rails initializer:

CreditCardValidations.configure do |config|
  config.source = '/path/to/my_brands.yml'
end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Open a Pull Request