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