Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Naranja card type #3299

Merged
merged 1 commit into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Realex: Prevent error calculating `refund_hash` or `credit_hash` when the secret is nil [jasonxp] #3291
* MercadoPago: Add Cabal card type [leila-alderman] #3295
* Adyen: Add app based 3DS requests for auth and purchase [jeremywrowe] #3298
* PayU Latam: Add Naranja card type [hdeters] #3299

== Version 1.96.0 (Jul 26, 2019)
* Bluesnap: Omit state codes for unsupported countries [therufs] #3229
Expand Down
2 changes: 2 additions & 0 deletions lib/active_merchant/billing/credit_card.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module Billing #:nodoc:
# * Elo
# * Alelo
# * Cabal
# * Naranja
#
# For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
# validations, allowing you to focus on your core concerns until you're ready to be more concerned
Expand Down Expand Up @@ -94,6 +95,7 @@ def number=(value)
# * +'elo'+
# * +'alelo'+
# * +'cabal'+
# * +'naranja'+
#
# Or, if you wish to test your implementation, +'bogus'+.
#
Expand Down
28 changes: 26 additions & 2 deletions lib/active_merchant/billing/credit_card_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module CreditCardMethods
'alelo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ALELO_RANGES) },
'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}|(62\d{14,17})$/ },
'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ },
'naranja' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), NARANJA_RANGES) },
'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11}$/ },
'jcb' => ->(num) { num =~ /^35(28|29|[3-8]\d)\d{12}$/ },
'dankort' => ->(num) { num =~ /^5019\d{12}$/ },
Expand Down Expand Up @@ -100,6 +101,10 @@ module CreditCardMethods
60352200..60352299
]

NARANJA_RANGES = [
589562..589562
]

def self.included(base)
base.extend(ClassMethods)
end
Expand Down Expand Up @@ -175,7 +180,7 @@ def valid_number?(number)
valid_test_mode_card_number?(number) ||
valid_card_number_length?(number) &&
valid_card_number_characters?(number) &&
valid_checksum?(number)
valid_by_algorithm?(brand?(number), number)
end

def card_companies
Expand Down Expand Up @@ -249,6 +254,15 @@ def valid_test_mode_card_number?(number) #:nodoc:
%w[1 2 3 success failure error].include?(number)
end

def valid_by_algorithm?(brand, numbers) #:nodoc:
case brand
when 'naranja'
valid_naranja_algo?(numbers)
else
valid_luhn?(numbers)
end
end

ODD_LUHN_VALUE = {
48 => 0,
49 => 1,
Expand Down Expand Up @@ -279,7 +293,7 @@ def valid_test_mode_card_number?(number) #:nodoc:
# Checks the validity of a card number by use of the Luhn Algorithm.
# Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
# This implementation is from the luhn_checksum gem, https://github.com/zendesk/luhn_checksum.
def valid_checksum?(numbers) #:nodoc:
def valid_luhn?(numbers) #:nodoc:
sum = 0

odd = true
Expand All @@ -295,6 +309,16 @@ def valid_checksum?(numbers) #:nodoc:

sum % 10 == 0
end

# Checks the validity of a card number by use of Naranja's specific algorithm.
def valid_naranja_algo?(numbers) #:nodoc:
num_array = numbers.to_s.chars.map(&:to_i)
multipliers = [4, 3, 2, 7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
num_sum = num_array[0..14].zip(multipliers).map { |a, b| a*b }.reduce(:+)
intermediate = 11 - (num_sum % 11)
final_num = intermediate > 9 ? 0 : intermediate
final_num == num_array[15]
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/adyen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AdyenGateway < Gateway
self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US']
self.default_currency = 'USD'
self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo]
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja]

self.money_format = :cents

Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/blue_snap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class BlueSnapGateway < Gateway
self.supported_countries = %w(US CA GB AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE)

self.default_currency = 'USD'
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja]

self.homepage_url = 'https://home.bluesnap.com/'
self.display_name = 'BlueSnap'
Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/d_local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class DLocalGateway < Gateway

self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY', 'TR']
self.default_currency = 'USD'
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja]

self.homepage_url = 'https://dlocal.com/'
self.display_name = 'dLocal'
Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/global_collect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class GlobalCollectGateway < Gateway
self.supported_countries = ['AD', 'AE', 'AG', 'AI', 'AL', 'AM', 'AO', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PL', 'PN', 'PS', 'PT', 'PW', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SR', 'ST', 'SV', 'SZ', 'TC', 'TD', 'TG', 'TH', 'TJ', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'US', 'UY', 'UZ', 'VC', 'VE', 'VG', 'VI', 'VN', 'WF', 'WS', 'ZA', 'ZM', 'ZW']
self.default_currency = 'USD'
self.money_format = :cents
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :naranja]

def initialize(options={})
requires!(options, :merchant_id, :api_key_id, :secret_api_key)
Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/mercado_pago.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class MercadoPagoGateway < Gateway
self.live_url = self.test_url = 'https://api.mercadopago.com/v1'

self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY']
self.supported_cardtypes = [:visa, :master, :american_express, :elo, :cabal]
self.supported_cardtypes = [:visa, :master, :american_express, :elo, :cabal, :naranja]

self.homepage_url = 'https://www.mercadopago.com/'
self.display_name = 'Mercado Pago'
Expand Down
5 changes: 3 additions & 2 deletions lib/active_merchant/billing/gateways/payu_latam.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ class PayuLatamGateway < Gateway
self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PA', 'PE']
self.default_currency = 'USD'
self.money_format = :dollars
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :naranja]

BRAND_MAP = {
'visa' => 'VISA',
'master' => 'MASTERCARD',
'american_express' => 'AMEX',
'diners_club' => 'DINERS'
'diners_club' => 'DINERS',
'naranja' => 'NARANJA'
}

MINIMUMS = {
Expand Down
3 changes: 2 additions & 1 deletion lib/active_merchant/billing/gateways/worldpay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class WorldpayGateway < Gateway
self.default_currency = 'GBP'
self.money_format = :cents
self.supported_countries = %w(HK GB AU AD AR BE BR CA CH CN CO CR CY CZ DE DK ES FI FR GI GR HU IE IN IT JP LI LU MC MT MY MX NL NO NZ PA PE PL PT SE SG SI SM TR UM VA)
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :elo]
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :elo, :naranja]
self.currencies_without_fractions = %w(HUF IDR ISK JPY KRW)
self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND)
self.homepage_url = 'http://www.worldpay.com/'
Expand All @@ -22,6 +22,7 @@ class WorldpayGateway < Gateway
'maestro' => 'MAESTRO-SSL',
'diners_club' => 'DINERS-SSL',
'elo' => 'ELO-SSL',
'naranja' => 'NARANJA-SSL',
'unknown' => 'CARD-SSL'
}

Expand Down
9 changes: 8 additions & 1 deletion test/remote/gateways/remote_d_local_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ class RemoteDLocalTest < Test::Unit::TestCase
def setup
@gateway = DLocalGateway.new(fixtures(:d_local))

@amount = 100
@amount = 200
@credit_card = credit_card('4111111111111111')
@credit_card_naranja = credit_card('5895627823453005')
# No test card numbers, all txns are approved by default,
# but errors can be invoked directly with the `description` field
@options = {
Expand Down Expand Up @@ -41,6 +42,12 @@ def test_successful_purchase
assert_match 'The payment was paid', response.message
end

def test_successful_purchase_naranja
response = @gateway.purchase(@amount, @credit_card_naranja, @options)
assert_success response
assert_match 'The payment was paid', response.message
end

def test_successful_purchase_with_more_options
options = @options.merge(
order_id: '1',
Expand Down
43 changes: 19 additions & 24 deletions test/remote/gateways/remote_payu_latam_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def setup
@credit_card = credit_card('4097440000000004', verification_value: '444', first_name: 'APPROVED', last_name: '')
@declined_card = credit_card('4097440000000004', verification_value: '444', first_name: 'REJECTED', last_name: '')
@pending_card = credit_card('4097440000000004', verification_value: '444', first_name: 'PENDING', last_name: '')
@naranja_credit_card = credit_card('5895620000000002', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'naranja')

@options = {
dni_number: '5415668464654',
Expand Down Expand Up @@ -49,6 +50,13 @@ def test_successful_purchase
assert response.test?
end

def test_successful_purchase_with_naranja_card
response = @gateway.purchase(@amount, @naranja_credit_card, @options)
assert_success response
assert_equal 'APPROVED', response.message
assert response.test?
end

def test_successful_purchase_with_specified_language
response = @gateway.purchase(@amount, @credit_card, @options.merge(language: 'es'))
assert_success response
Expand Down Expand Up @@ -231,6 +239,13 @@ def test_successful_authorize
assert_match %r(^\d+\|(\w|-)+$), response.authorization
end

def test_successful_authorize_with_naranja_card
response = @gateway.authorize(@amount, @naranja_credit_card, @options)
assert_success response
assert_equal 'APPROVED', response.message
assert_match %r(^\d+\|(\w|-)+$), response.authorization
end

def test_successful_authorize_with_specified_language
response = @gateway.authorize(@amount, @credit_card, @options.merge(language: 'es'))
assert_success response
Expand Down Expand Up @@ -299,16 +314,6 @@ def test_failed_refund_with_specified_language
assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message)
end

# If this test fails, support for void may have been added to the sandbox
def test_unsupported_test_void_fails_as_expected
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth

assert void = @gateway.void(auth.authorization)
assert_failure void
assert_equal 'Internal payment provider error. ', void.message
end

def test_failed_void
response = @gateway.void('')
assert_failure response
Expand All @@ -321,16 +326,6 @@ def test_failed_void_with_specified_language
assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message)
end

# If this test fails, support for captures may have been added to the sandbox
def test_unsupported_test_capture_fails_as_expected
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth

assert capture = @gateway.capture(@amount, auth.authorization, @options)
assert_failure capture
assert_equal 'Internal payment provider error. ', capture.message
end

def test_failed_capture
response = @gateway.capture(@amount, '')
assert_failure response
Expand Down Expand Up @@ -366,17 +361,17 @@ def test_successful_verify_with_specified_language
end

def test_failed_verify_with_specified_amount
verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 1699))
verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 499))

assert_failure verify
assert_equal 'The order value is less than minimum allowed. Minimum value allowed 17 ARS', verify.message
assert_equal 'The order value is less than minimum allowed. Minimum value allowed 5 ARS', verify.message
end

def test_failed_verify_with_specified_language
verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 1699, language: 'es'))
verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 499, language: 'es'))

assert_failure verify
assert_equal 'The order value is less than minimum allowed. Minimum value allowed 17 ARS', verify.message
assert_equal 'The order value is less than minimum allowed. Minimum value allowed 5 ARS', verify.message
end

def test_transcript_scrubbing
Expand Down
12 changes: 12 additions & 0 deletions test/unit/credit_card_methods_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ def test_should_detect_alelo_card
assert_equal 'alelo', CreditCard.brand?('5067600000000044')
end

def test_should_detect_naranja_card
assert_equal 'naranja', CreditCard.brand?('5895627823453005')
assert_equal 'naranja', CreditCard.brand?('5895620000000002')
assert_equal 'naranja', CreditCard.brand?('5895626746595650')
end

# Alelo BINs beginning with the digit 4 overlap with Visa's range of valid card numbers.
# We intentionally misidentify these cards as Visa, which works because transactions with
# such cards will run on Visa rails.
Expand Down Expand Up @@ -199,6 +205,12 @@ def test_matching_invalid_card
assert_false CreditCard.valid_number?(nil)
end

def test_matching_valid_naranja
number = '5895627823453005'
assert_equal 'naranja', CreditCard.brand?(number)
assert CreditCard.valid_number?(number)
end

def test_16_digit_maestro_uk
number = '6759000000000000'
assert_equal 16, number.length
Expand Down