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 021import org.jpos.iso.ISODate; 022import org.jpos.iso.ISOMsg; 023import org.jpos.iso.ISOUtil; 024 025import java.math.BigInteger; 026import java.util.Date; 027import java.util.Objects; 028 029/** 030 * @author apr@jpos.org 031 * @since jPOS 2.0.5 032 * 033 * This class is based on the old 'CardHolder' class and adds support for multiple 034 * PAN and Expiration dates taken from manual entry, track1, track2. It also corrects the name. 035 */ 036public class Card { 037 private String pan; 038 private String exp; 039 private String cvv2; 040 private String serviceCode; 041 private Track1 track1; 042 private Track2 track2; 043 public static final int BINLEN = 6; 044 045 private Card() { } 046 047 public Card(Builder builder) { 048 pan = builder.pan; 049 exp = builder.exp; 050 cvv2 = builder.cvv2; 051 serviceCode = builder.serviceCode; 052 track1 = builder.track1; 053 track2 = builder.track2; 054 } 055 056 public String getPan() { 057 return pan; 058 } 059 060 public BigInteger getPanAsNumber() { 061 return new BigInteger(pan); 062 } 063 064 public String getExp() { 065 return exp; 066 } 067 068 public String getCvv2() { 069 return cvv2; 070 } 071 072 public String getServiceCode() { 073 return serviceCode; 074 } 075 076 public boolean hasTrack1() { 077 return track1 != null; 078 } 079 080 public boolean hasTrack2() { 081 return track2 != null; 082 } 083 084 public boolean hasBothTracks() { 085 return hasTrack1() && hasTrack2(); 086 } 087 088 /** 089 * Returns the traditional 6-digit BIN from the PAN 090 * @return the first 6 digits of the PAN 091 */ 092 public String getBin () { 093 return getBin(BINLEN); 094 } 095 096 /** 097 * Returns the first <code>len</code> digits from the PAN. 098 * Can be used for the newer 8-digit BINs, or some arbitrary length. 099 * @return the first <code>len</code> digits of the PAN 100 */ 101 public String getBin (int len) { 102 return pan.substring(0, len); 103 } 104 105 @Override 106 public String toString() { 107 return pan != null ? ISOUtil.protect(pan) : "nil"; 108 } 109 110 @Override 111 public boolean equals(Object o) { 112 if (this == o) return true; 113 if (o == null || getClass() != o.getClass()) return false; 114 Card card = (Card) o; 115 return Objects.equals(pan, card.pan) && 116 Objects.equals(exp, card.exp) && 117 Objects.equals(cvv2, card.cvv2) && 118 Objects.equals(serviceCode, card.serviceCode) && 119 Objects.equals(track1, card.track1) && 120 Objects.equals(track2, card.track2); 121 } 122 123 @Override 124 public int hashCode() { 125 return Objects.hash(pan, exp, cvv2, serviceCode, track1, track2); 126 } 127 128 public Track1 getTrack1() { 129 return track1; 130 } 131 132 public Track2 getTrack2() { 133 return track2; 134 } 135 136 public boolean isExpired (Date currentDate) { 137 if (exp == null || exp.length() != 4) 138 return true; 139 String now = ISODate.formatDate(currentDate, "yyyyMM"); 140 try { 141 int mm = Integer.parseInt(exp.substring(2)); 142 int aa = Integer.parseInt(exp.substring(0,2)); 143 if (aa < 100 && mm > 0 && mm <= 12) { 144 String expDate = (aa < 70 ? "20" : "19") + exp; 145 if (expDate.compareTo(now) >= 0) 146 return false; 147 } 148 } catch (NumberFormatException ignored) { 149 // NOPMD 150 } 151 return true; 152 } 153 154 public static Builder builder() { 155 return new Builder(); 156 } 157 158 public static class Builder { 159 public static CardValidator DEFAULT_CARD_VALIDATOR = new DefaultCardValidator(); 160 private String pan; 161 private String exp; 162 private String cvv; 163 private String cvv2; 164 private String serviceCode; 165 private Track1 track1; 166 private Track2 track2; 167 private Track1.Builder track1Builder = Track1.builder(); 168 private Track2.Builder track2Builder = Track2.builder(); 169 private CardValidator validator = DEFAULT_CARD_VALIDATOR; 170 171 private Builder () { } 172 public Builder pan (String pan) { this.pan = pan; return this; } 173 public Builder exp (String exp) { this.exp = exp; return this; } 174 public Builder cvv (String cvv) { this.cvv = cvv; return this; } 175 public Builder cvv2 (String cvv2) { this.cvv2 = cvv2; return this; } 176 public Builder serviceCode (String serviceCode) { this.serviceCode = serviceCode; return this; } 177 public Builder validator (CardValidator validator) { 178 this.validator = validator; 179 return this; 180 } 181 public Builder withTrack1Builder (Track1.Builder track1Builder) { 182 this.track1Builder = track1Builder; 183 return this; 184 } 185 public Builder withTrack2Builder (Track2.Builder track2Builder) { 186 this.track2Builder = track2Builder; 187 return this; 188 } 189 public Builder track1 (Track1 track1) { 190 this.track1 = track1; 191 return this; 192 } 193 public Builder track2 (Track2 track2) { 194 this.track2 = track2; 195 return this; 196 } 197 public Builder isomsg (ISOMsg m) throws InvalidCardException { 198 if (m.hasField(2)) 199 pan(m.getString(2)); 200 if (m.hasField(14)) 201 exp(m.getString(14)); 202 if (m.hasField(35)) 203 track2(track2Builder.track(m.getString(35)).build()); 204 if (m.hasField(45)) 205 track1(track1Builder.track(m.getString(45)).build()); 206 if (pan == null && track2 != null) 207 pan (track2.getPan()); 208 if (pan == null && track1 != null) 209 pan (track1.getPan()); 210 if (exp == null && track2 != null) 211 exp (track2.getExp()); 212 if (exp == null && track1 != null) 213 exp (track1.getExp()); 214 if (track2 != null) { 215 if (pan == null) 216 pan (track2.getPan()); 217 if (exp == null) 218 exp (track2.getExp()); 219 if (serviceCode == null) 220 serviceCode(track2.getServiceCode()); 221 } 222 if (track1 != null) { 223 if (pan == null) 224 pan (track1.getPan()); 225 if (exp == null) 226 exp (track1.getExp()); 227 if (serviceCode == null) 228 serviceCode(track1.getServiceCode()); 229 } 230 return this; 231 } 232 233 public Card build() throws InvalidCardException { 234 Card c = new Card(this); 235 if (validator != null) 236 validator.validate(c); 237 return c; 238 } 239 240 } 241}