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.ISOUtil; 022 023import java.util.Objects; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027/** 028 * @author apr@jpos.org 029 * @since jPOS 2.0.5 030 * 031 * This class is based on the old 'CardHolder' class and adds support for multiple 032 * PAN and Expiration dates taken from manual entry, track1, track2. It also corrects the name. 033 */ 034@SuppressWarnings("unused") 035public class Track2 { 036 private String pan; 037 private String exp; 038 private String cvv; 039 private String serviceCode; 040 private String discretionaryData; 041 private String track; 042 043 private Track2 () { } 044 045 public Track2 (Builder builder) { 046 track = builder.track; 047 pan = builder.pan; 048 exp = builder.exp; 049 cvv = builder.cvv; 050 serviceCode = builder.serviceCode; 051 discretionaryData = builder.discretionaryData; 052 } 053 054 public String getPan() { 055 return pan; 056 } 057 058 public String getExp() { 059 return exp; 060 } 061 062 public String getCvv() { 063 return cvv; 064 } 065 066 public String getServiceCode() { 067 return serviceCode; 068 } 069 070 public String getDiscretionaryData() { 071 return discretionaryData; 072 } 073 074 public String getTrack() { 075 return track; 076 } 077 078 @Override 079 public String toString() { 080 return pan != null ? ISOUtil.protect(pan) : "nil"; 081 } 082 083 @Override 084 public boolean equals(Object o) { 085 if (this == o) return true; 086 if (o == null || getClass() != o.getClass()) return false; 087 Track2 track21 = (Track2) o; 088 return Objects.equals(pan, track21.pan) && 089 Objects.equals(exp, track21.exp) && 090 Objects.equals(cvv, track21.cvv) && 091 Objects.equals(serviceCode, track21.serviceCode) && 092 Objects.equals(discretionaryData, track21.discretionaryData) && 093 Objects.equals(track, track21.track); 094 } 095 096 @Override 097 public int hashCode() { 098 return Objects.hash(pan, exp, cvv, serviceCode, discretionaryData, track); 099 } 100 101 public static Builder builder() { 102 return new Builder(); 103 } 104 105 public static class Builder { 106 private static String TRACK2_EXPR = "^([0-9]{1,19})[=D]([0-9]{4})?([0-9]{3})?([0-9]{4})?([0-9]{1,})?$"; 107 private static Pattern TRACK2_PATTERN = Pattern.compile(TRACK2_EXPR); 108 private String pan; 109 private String exp; 110 private String cvv; 111 private String serviceCode; 112 private String discretionaryData; 113 private String track; 114 private Pattern pattern = TRACK2_PATTERN; 115 116 private Builder () { } 117 118 public Builder pan (String pan) { 119 this.pan = pan; return this; 120 } 121 122 public Builder exp (String exp) { 123 this.exp = exp; return this; 124 } 125 126 public Builder cvv (String cvv) { 127 this.cvv = cvv; return this; 128 } 129 130 public Builder serviceCode (String serviceCode) { 131 this.serviceCode = serviceCode; return this; 132 } 133 134 public Builder discretionaryData (String discretionaryData) { 135 this.discretionaryData = discretionaryData; 136 return this; 137 } 138 139 /** 140 * Optional method, can be used to override default pattern 141 * @param pattern overrides default pattern 142 * @return this builder 143 */ 144 public Builder pattern (Pattern pattern) { 145 this.pattern = pattern; 146 return this; 147 } 148 149 public Builder track (String s) 150 throws InvalidCardException 151 { 152 if (s == null) 153 throw new InvalidCardException ("null track2 data"); 154 if (s.length() > 37) 155 throw new InvalidCardException("track2 too long"); 156 157 track = s; 158 Matcher matcher = pattern.matcher(s); 159 int cnt = matcher.groupCount(); 160 if (matcher.find() && cnt >= 1) { 161 pan = matcher.group(1); 162 if (cnt > 1) 163 exp = matcher.group(2); 164 if (cnt > 2) 165 serviceCode = matcher.group(3); 166 if (cnt > 3) 167 cvv = matcher.group(4); 168 if (cnt > 4) 169 discretionaryData = matcher.group(5); 170 } else { 171 throw new InvalidCardException ("invalid track2"); 172 } 173 return this; 174 } 175 176 /** 177 * Constructs the Track2 data based on the card data provided. 178 * The generated Track2 data is validated using the pattern. 179 * If the Track2 data doesn't match the pattern, the track attribute keeps the original value. 180 * @return this builder. 181 */ 182 public Builder buildTrackData() { 183 StringBuilder track2 = new StringBuilder(this.pan); 184 track2.append("="); 185 track2.append(this.exp); 186 track2.append(this.serviceCode); 187 track2.append(this.cvv); 188 track2.append(this.discretionaryData); 189 190 Matcher matcher = this.pattern.matcher(track2); 191 int cnt = matcher.groupCount(); 192 if (matcher.find() && cnt >= 1) 193 this.track = track2.toString(); 194 195 return this; 196 } 197 198 public Track2 build() { 199 if (this.track == null) 200 buildTrackData(); 201 return new Track2(this); 202 } 203 } 204}