001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.core; 020 021/** 022 * Default {@link CardValidator} that cross-checks the PAN, expiration, and 023 * service code across explicit fields and embedded tracks 1/2, and verifies 024 * the LUHN check digit using a configurable {@link LUHNCalculator}. 025 */ 026public class DefaultCardValidator implements CardValidator { 027 /** Default constructor; no instance state to initialise. */ 028 public DefaultCardValidator() {} 029 private static LUHNCalculator DEFAULT_LUHN_CALCULATOR = new DefaultLUHNCalculator(); 030 private LUHNCalculator luhnCalculator = DEFAULT_LUHN_CALCULATOR; 031 032 public void validate (Card card) throws InvalidCardException { 033 if (card != null) { 034 String pan = card.getPan(); 035 if (pan != null) { 036 if (card.getTrack1() != null && !pan.equals(card.getTrack1().getPan())) 037 throw new InvalidCardException ("track1 PAN mismatch"); 038 if (card.getTrack2() != null && !pan.equals(card.getTrack2().getPan())) 039 throw new InvalidCardException ("track2 PAN mismatch"); 040 } 041 String exp = card.getExp(); 042 if (exp != null) { 043 if (card.getTrack1() != null && !exp.equals(card.getTrack1().getExp())) 044 throw new InvalidCardException ("track1 EXP mismatch"); 045 if (card.getTrack2() != null && !exp.equals(card.getTrack2().getExp())) 046 throw new InvalidCardException ("track2 EXP mismatch"); 047 } 048 if (card.getServiceCode() != null) { 049 int mismatch = 0; 050 if (card.hasBothTracks()) { 051 if (card.getTrack2().getServiceCode() != null) { 052 if (!card.getTrack2().getServiceCode().equals(card.getServiceCode())) 053 mismatch++; 054 if (!card.getTrack2().getServiceCode().equals(card.getTrack1().getServiceCode())) 055 mismatch++; 056 } 057 } else if (card.hasTrack2()) { 058 if (card.getTrack2().getServiceCode() != null) { 059 if (!card.getTrack2().getServiceCode().equals(card.getServiceCode())) 060 mismatch++; 061 } 062 } else if (card.hasTrack1()) { 063 if (card.getTrack1().getServiceCode() != null) { 064 if (!card.getTrack1().getServiceCode().equals(card.getServiceCode())) 065 mismatch++; 066 } 067 } 068 if (mismatch > 0) { 069 throw new InvalidCardException(String.format("service code mismatch (%d)", mismatch)); 070 } 071 } 072 if (luhnCalculator != null && !luhnCalculator.verify(pan)) 073 throw new InvalidCardException ("invalid LUHN"); 074 } 075 } 076 077 /** 078 * Replaces the {@link LUHNCalculator} used to verify PAN check digits. 079 * 080 * @param luhnCalculator new calculator (or {@code null} to skip LUHN verification) 081 */ 082 public void setLuhnCalculator(LUHNCalculator luhnCalculator) { 083 this.luhnCalculator = luhnCalculator; 084 } 085}