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}