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