001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.model.control.payment.server.logic; 018 019import com.echothree.control.user.payment.common.edit.PartyPaymentMethodEdit; 020import com.echothree.model.control.contact.server.control.ContactControl; 021import com.echothree.model.control.party.common.PartyTypes; 022import com.echothree.model.control.party.server.control.PartyControl; 023import com.echothree.model.control.payment.common.PaymentMethodTypes; 024import com.echothree.model.control.payment.common.exception.UnknownPartyPaymentMethodNameException; 025import com.echothree.model.control.payment.server.control.PartyPaymentMethodControl; 026import com.echothree.model.control.payment.server.control.PaymentMethodControl; 027import com.echothree.model.control.user.server.control.UserControl; 028import com.echothree.model.data.party.common.pk.PartyPK; 029import com.echothree.model.data.party.server.entity.Party; 030import com.echothree.model.data.payment.server.entity.PartyPaymentMethod; 031import com.echothree.model.data.payment.server.entity.PaymentMethod; 032import com.echothree.model.data.payment.server.entity.PaymentMethodCreditCard; 033import com.echothree.model.data.user.server.entity.UserVisit; 034import com.echothree.util.common.message.ExecutionErrors; 035import com.echothree.util.server.control.BaseLogic; 036import com.echothree.util.server.message.ExecutionErrorAccumulator; 037import com.echothree.util.server.persistence.EntityPermission; 038import com.echothree.util.server.persistence.Session; 039import java.time.Instant; 040import java.time.ZoneId; 041import java.time.ZonedDateTime; 042import java.util.regex.Pattern; 043import javax.enterprise.context.ApplicationScoped; 044import javax.inject.Inject; 045 046@ApplicationScoped 047public class PartyPaymentMethodLogic 048 extends BaseLogic { 049 050 @Inject 051 ContactControl contactControl; 052 053 @Inject 054 PartyControl partyControl; 055 056 @Inject 057 PartyPaymentMethodControl partyPaymentMethodControl; 058 059 @Inject 060 PaymentMethodControl paymentMethodControl; 061 062 @Inject 063 UserControl userControl; 064 065 protected PartyPaymentMethodLogic() { 066 super(); 067 } 068 069 private String getDigitsOnly(String s) { 070 var digitsOnly = new StringBuilder(); 071 072 for(var i = 0; i < s.length(); i++) { 073 var c = s.charAt(i); 074 075 if(Character.isDigit(c)) { 076 digitsOnly.append(c); 077 } 078 } 079 080 return digitsOnly.toString(); 081 } 082 083 private boolean isValid(String number) { 084 var digitsOnly = getDigitsOnly(number); 085 var sum = 0; 086 var timesTwo = false; 087 088 for(var i = digitsOnly.length() - 1; i >= 0; i--) { 089 var digit = Integer.parseInt(digitsOnly.substring(i, i + 1)); 090 int addend; 091 092 if(timesTwo) { 093 addend = digit * 2; 094 095 if (addend > 9) { 096 addend -= 9; 097 } 098 } else { 099 addend = digit; 100 } 101 102 sum += addend; 103 timesTwo = !timesTwo; 104 } 105 106 var modulus = sum % 10; 107 108 return modulus == 0; 109 } 110 111 public void checkPartyType(final ExecutionErrorAccumulator ema, final Party party) { 112 var partyTypeName = party.getLastDetail().getPartyType().getPartyTypeName(); 113 114 if(!partyTypeName.equals(PartyTypes.CUSTOMER.name())) { 115 ema.addExecutionError(ExecutionErrors.InvalidPartyType.name(), partyTypeName); 116 } 117 } 118 119 public void checkNameOnCard(final ExecutionErrorAccumulator ema, final PartyPaymentMethodEdit ppme, final PaymentMethodCreditCard paymentMethodCreditCard) { 120 var personalTitleId = ppme.getPersonalTitleId(); 121 var personalTitle = personalTitleId == null? null: partyControl.convertPersonalTitleIdToEntity(personalTitleId, EntityPermission.READ_ONLY); 122 123 if(personalTitleId == null || personalTitle != null) { 124 var nameSuffixId = ppme.getNameSuffixId(); 125 var nameSuffix = nameSuffixId == null? null: partyControl.convertNameSuffixIdToEntity(nameSuffixId, EntityPermission.READ_ONLY); 126 127 if(nameSuffixId == null || nameSuffix != null) { 128 if(paymentMethodCreditCard.getRequireNameOnCard()) { 129 if(ppme.getFirstName() == null || ppme.getLastName() == null) { 130 ema.addExecutionError(ExecutionErrors.MissingNameOnCard.name()); 131 } 132 } 133 } else { 134 ema.addExecutionError(ExecutionErrors.UnknownNameSuffixId.name()); 135 } 136 } else { 137 ema.addExecutionError(ExecutionErrors.UnknownPersonalTitleId.name()); 138 } 139 } 140 141 public void checkNumber(final ExecutionErrorAccumulator ema, final PartyPaymentMethodEdit ppme, final PaymentMethodCreditCard paymentMethodCreditCard) { 142 var number = ppme.getNumber(); 143 144 if(number != null) { 145 var cardNumberValidationPattern = paymentMethodCreditCard.getCardNumberValidationPattern(); 146 var validCardNumber = true; 147 148 if(cardNumberValidationPattern != null) { 149 var m = Pattern.compile(cardNumberValidationPattern).matcher(number); 150 151 if(!m.matches()) { 152 validCardNumber = false; 153 } 154 } 155 156 if(!validCardNumber || !isValid(number)) { 157 ema.addExecutionError(ExecutionErrors.InvalidNumber.name()); 158 } 159 } else { 160 ema.addExecutionError(ExecutionErrors.MissingNumber.name()); 161 } 162 } 163 164 public void checkExpirationDate(final Session session, final ExecutionErrorAccumulator ema, final Party party, final PartyPaymentMethodEdit ppme, 165 final PaymentMethodCreditCard paymentMethodCreditCard) { 166 var strExpirationMonth = ppme.getExpirationMonth(); 167 var strExpirationYear = ppme.getExpirationYear(); 168 169 if(strExpirationMonth != null && strExpirationYear != null) { 170 if(paymentMethodCreditCard.getCheckExpirationDate()) { 171 var timeZone = userControl.getPreferredTimeZoneFromParty(party); 172 int expirationMonth = Integer.valueOf(strExpirationMonth); 173 int expirationYear = Integer.valueOf(strExpirationYear); 174 var dt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(session.getStartTime()), ZoneId.of(timeZone.getLastDetail().getJavaTimeZoneName())); 175 var validExpirationDate = true; 176 var thisYear = dt.getYear(); 177 178 if(!(expirationYear < thisYear)) { 179 if(expirationYear == thisYear && expirationMonth < dt.getMonthValue()) { 180 validExpirationDate = false; 181 } 182 } else { 183 validExpirationDate = false; 184 } 185 186 if(!validExpirationDate) { 187 ema.addExecutionError(ExecutionErrors.InvalidExpirationDate.name(), strExpirationMonth, strExpirationYear); 188 } 189 } 190 } else if(paymentMethodCreditCard.getRequireExpirationDate()) { 191 if(strExpirationMonth == null) { 192 ema.addExecutionError(ExecutionErrors.MissingExpirationMonth.name()); 193 } 194 195 if(strExpirationYear == null) { 196 ema.addExecutionError(ExecutionErrors.MissingExpirationYear.name()); 197 } 198 } 199 } 200 201 public void checkSecurityCode(final ExecutionErrorAccumulator ema, final PartyPaymentMethodEdit ppme, final PaymentMethodCreditCard paymentMethodCreditCard) { 202 var securityCode = ppme.getSecurityCode(); 203 204 if(securityCode != null) { 205 var securityCodeValidationPattern = paymentMethodCreditCard.getSecurityCodeValidationPattern(); 206 var validSecurityCode = true; 207 208 if(securityCodeValidationPattern != null) { 209 var m = Pattern.compile(securityCodeValidationPattern).matcher(securityCode); 210 211 if(!m.matches()) { 212 validSecurityCode = false; 213 } 214 } 215 216 if(!validSecurityCode) { 217 ema.addExecutionError(ExecutionErrors.InvalidSecurityCode.name()); 218 } 219 } else if(paymentMethodCreditCard.getRequireSecurityCode()) { 220 ema.addExecutionError(ExecutionErrors.MissingSecurityCode.name()); 221 } 222 } 223 224 public void checkBillingContactMechanism(final ExecutionErrorAccumulator ema, final Party party, final PartyPaymentMethodEdit ppme, 225 final PaymentMethodCreditCard paymentMethodCreditCard) { 226 var billingContactMechanismName = ppme.getBillingContactMechanismName(); 227 var billingContactMechanism = billingContactMechanismName == null? null: contactControl.getContactMechanismByName(billingContactMechanismName); 228 229 if(billingContactMechanism != null) { 230 var billingPartyContactMechanism = billingContactMechanism == null? null: contactControl.getPartyContactMechanism(party, billingContactMechanism); 231 232 if(billingPartyContactMechanism == null) { 233 ema.addExecutionError(ExecutionErrors.UnknownPartyContactMechanism.name(), party.getLastDetail().getPartyName(), billingContactMechanismName); 234 } 235 } else { 236 if(billingContactMechanismName != null && billingContactMechanism == null) { 237 ema.addExecutionError(ExecutionErrors.UnknownBillingContactMechanismName.name(), billingContactMechanismName); 238 } else if(paymentMethodCreditCard.getRequireBilling()) { 239 ema.addExecutionError(ExecutionErrors.MissingBillingContactMechanismName.name()); 240 } 241 } 242 } 243 244 public void checkIssuer(final ExecutionErrorAccumulator ema, final Party party, final PartyPaymentMethodEdit ppme, 245 final PaymentMethodCreditCard paymentMethodCreditCard) { 246 var issuerName = ppme.getIssuerName(); 247 var issuerContactMechanismName = ppme.getIssuerContactMechanismName(); 248 var issuerContactMechanism = issuerContactMechanismName == null ? null : contactControl.getContactMechanismByName(issuerContactMechanismName); 249 250 if(issuerName != null && issuerContactMechanism != null) { 251 var issuerPartyContactMechanism = issuerContactMechanism == null ? null : contactControl.getPartyContactMechanism(party, issuerContactMechanism); 252 253 if(issuerPartyContactMechanism == null) { 254 ema.addExecutionError(ExecutionErrors.UnknownPartyContactMechanism.name(), party.getLastDetail().getPartyName(), issuerContactMechanismName); 255 } 256 } else { 257 if(paymentMethodCreditCard.getRequireIssuer()) { 258 if(issuerName == null) { 259 ema.addExecutionError(ExecutionErrors.MissingIssuerName.name()); 260 } 261 262 if(issuerContactMechanismName != null && issuerContactMechanism == null) { 263 ema.addExecutionError(ExecutionErrors.UnknownIssuerContactMechanismName.name(), issuerContactMechanismName); 264 } else { 265 ema.addExecutionError(ExecutionErrors.MissingIssuerContactMechanismName.name()); 266 } 267 } else { 268 if(issuerContactMechanismName != null && issuerContactMechanism == null) { 269 ema.addExecutionError(ExecutionErrors.UnknownIssuerContactMechanismName.name(), issuerContactMechanismName); 270 } 271 } 272 } 273 } 274 275 public void checkCreditCard(final Session session, final ExecutionErrorAccumulator ema, final Party party, final PaymentMethod paymentMethod, 276 final PartyPaymentMethodEdit ppme) { 277 var paymentMethodCreditCard = paymentMethodControl.getPaymentMethodCreditCard(paymentMethod); 278 279 if(paymentMethodCreditCard.getRequestNameOnCard()) { 280 checkNameOnCard(ema, ppme, paymentMethodCreditCard); 281 } else { 282 ppme.setPersonalTitleId(null); 283 ppme.setFirstName(null); 284 ppme.setMiddleName(null); 285 ppme.setLastName(null); 286 ppme.setNameSuffixId(null); 287 ppme.setName(null); 288 } 289 290 if(paymentMethodCreditCard.getCheckCardNumber()) { 291 checkNumber(ema, ppme, paymentMethodCreditCard); 292 } 293 294 if(paymentMethodCreditCard.getRequestExpirationDate()) { 295 checkExpirationDate(session, ema, party, ppme, paymentMethodCreditCard); 296 } else { 297 ppme.setExpirationMonth(null); 298 ppme.setExpirationYear(null); 299 } 300 301 if(paymentMethodCreditCard.getRequestSecurityCode()) { 302 checkSecurityCode(ema, ppme, paymentMethodCreditCard); 303 } else { 304 ppme.setSecurityCode(null); 305 } 306 307 if(paymentMethodCreditCard.getRequestBilling()) { 308 checkBillingContactMechanism(ema, party, ppme, paymentMethodCreditCard); 309 } else { 310 ppme.setBillingContactMechanismName(null); 311 } 312 313 if(paymentMethodCreditCard.getRequestIssuer()) { 314 checkIssuer(ema, party, ppme, paymentMethodCreditCard); 315 } else { 316 ppme.setIssuerName(null); 317 ppme.setIssuerContactMechanismName(null); 318 } 319 } 320 321 public void checkPaymentMethodType(final Session session, final ExecutionErrorAccumulator ema, final Party party, final PaymentMethod paymentMethod, 322 final PartyPaymentMethodEdit ppme) { 323 var paymentMethodType = paymentMethod.getLastDetail().getPaymentMethodType(); 324 var paymentMethodTypeName = paymentMethodType.getLastDetail().getPaymentMethodTypeName(); 325 326 if(paymentMethodTypeName.equals(PaymentMethodTypes.CREDIT_CARD.name())) { 327 checkCreditCard(session, ema, party, paymentMethod, ppme); 328 } else { 329 ema.addExecutionError(ExecutionErrors.InvalidPaymentMethodType.name(), paymentMethodTypeName); 330 } 331 } 332 333 public void checkPartyPaymentMethod(final Session session, final UserVisit userVisit, final ExecutionErrorAccumulator ema, final Party party, 334 final PaymentMethod paymentMethod, final PartyPaymentMethodEdit ppme) { 335 checkPartyType(ema, party); 336 337 if(!ema.hasExecutionErrors()) { 338 checkPaymentMethodType(session, ema, party, paymentMethod, ppme); 339 } 340 } 341 342 private PartyPaymentMethod getPartyPaymentMethodByName(final ExecutionErrorAccumulator eea, final String partyPaymentMethodName, 343 final EntityPermission entityPermission) { 344 var partyPaymentMethod = partyPaymentMethodControl.getPartyPaymentMethodByName(partyPaymentMethodName, entityPermission); 345 346 if(partyPaymentMethod == null) { 347 handleExecutionError(UnknownPartyPaymentMethodNameException.class, eea, ExecutionErrors.UnknownPartyPaymentMethodName.name(), partyPaymentMethodName); 348 } 349 350 return partyPaymentMethod; 351 } 352 353 public PartyPaymentMethod getPartyPaymentMethodByName(final ExecutionErrorAccumulator eea, 354 final String partyPaymentMethodName) { 355 return getPartyPaymentMethodByName(eea, partyPaymentMethodName, EntityPermission.READ_ONLY); 356 } 357 358 public PartyPaymentMethod getPartyPaymentMethodByNameForUpdate(final ExecutionErrorAccumulator eea, 359 final String partyPaymentMethodName) { 360 return getPartyPaymentMethodByName(eea, partyPaymentMethodName, EntityPermission.READ_WRITE); 361 } 362 363 public void deletePartyPaymentMethod(final ExecutionErrorAccumulator eea, final PartyPaymentMethod partyPaymentMethod, 364 final PartyPK deletedBy) { 365 // TODO: Check to see if this payment method is in use on any open orders, 366 // or orders that currently are allowing returns to be made against them. 367 // If that's the case, the PPM shouldn't be deleted. 368 partyPaymentMethodControl.deletePartyPaymentMethod(partyPaymentMethod, deletedBy); 369 } 370 371 public void deletePartyPaymentMethod(final ExecutionErrorAccumulator eea, final String partyPaymentMethodName, 372 final PartyPK deletedBy) { 373 var partyPaymentMethod = getPartyPaymentMethodByNameForUpdate(eea, partyPaymentMethodName); 374 375 if(eea == null || !eea.hasExecutionErrors()) { 376 deletePartyPaymentMethod(eea, partyPaymentMethod, deletedBy); 377 } 378 } 379 380}