Java tutorial
/* * Copyright (C) 2017 FormKiQ Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.formkiq.core.equifax.service; import static com.formkiq.core.form.FormFinder.findValueByKey; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.tuple.Pair; import org.beanio.UnidentifiedRecordException; import org.springframework.util.StringUtils; import com.formkiq.core.equifax.segment.CERRSegment; import com.formkiq.core.equifax.segment.ScorSegment; import com.formkiq.core.equifax.segment.Segment; import com.formkiq.core.equifax.segment.SegmentFactory; import com.formkiq.core.form.dto.FormJSON; import com.formkiq.core.form.dto.FormJSONValidator; import com.formkiq.core.form.dto.FormJSONValidatorRuleType; /** * Equifax Response Processor. * */ public class EquifaxResponseProcessor { /** Regex Pattern for determining Approved amount. */ private static final Pattern UNDER_PATTERN = Pattern.compile("(.*)(APPROVED FOR ITEMS UNDER)(.*)"); /** Date Formatter for adding commas. */ private final DecimalFormat formatter = new DecimalFormat("#,###"); /** Format Error Map. */ private Map<String, String> formatErrors = new HashMap<>(); /** Processing Error Map. */ private Map<String, String> processingErrors = new HashMap<>(); /** Validity Error Map. */ private Map<String, String> validityErrors = new HashMap<>(); /** * Create Validation Rules based on Equifax Response. * @param form {@link FormJSON} * @param response {@link String} * @return {@link Map} */ public Map<String, List<FormJSONValidator>> createValidators(final FormJSON form, final String response) { createFormatErrorMap(); createProcessingErrorMap(); createValidityErrorMap(); Map<String, List<FormJSONValidator>> map = new HashMap<>(); SegmentFactory factory = new SegmentFactory(); Pair<ScorSegment, CERRSegment> segments = findScorSegment(factory, response); ScorSegment scor = segments.getLeft(); CERRSegment err = segments.getRight(); if (scor != null) { map = processScorSegment(scor, form); } else if (err != null) { String error = getErrorCode(err); addValidation(map, FormJSONValidatorRuleType.STOP, error); } else { String error = "Unable to process Equifax Request. " + "Please try again later."; addValidation(map, FormJSONValidatorRuleType.STOP, error); } return map; } /** * Create Format Error Map. */ private void createFormatErrorMap() { Map<String, String> f = new HashMap<>(); f.put("021", "Segment Count Not Equal to Segments In"); f.put("023", "Invalid Card Alert Request Code Used in the " + "IDNT Segment"); f.put("024", "Invalid Deposit Exchange Code Used in the IDNT Segment"); f.put("026", "Member Number/Security Code Invalid (IDNT)"); f.put("027", "Segment Count Non-Numeric"); f.put("028", "ECOA Inquiry Type Invalid"); f.put("029", "Type of Processing Code Invalid"); f.put("02A", "Number of Months to Count Inquiries Invalid (IDNT)"); f.put("02B", "Number of Months to Count Maximum Delinquency " + "Invalid (IDNT)"); f.put("02C", "Invalid Segment Identifier"); f.put("02D", "This Code is not Presently Used"); f.put("02E", "This Code is not Presently Used"); f.put("02F", "Flash Format Error"); f.put("022", "Maximum Number of Segments Exceeded"); f.put("025", "AD Segment Does Not Follow IDNT Segment"); this.formatErrors = f; } /** * Create Processing Error Map. */ private void createProcessingErrorMap() { Map<String, String> p = new HashMap<>(); p.put("002", "No Record Found"); p.put("008", "This Area of Records Temporarily Unavailable " + "- Please Try Later"); p.put("009", "This Area of Records Temporarily Unavailable " + "- Please Try Later"); p.put("00A", "Your Transaction Was Not Completed - Error Has " + "Occurred in Searching File"); p.put("014", "Re-enter Your Entire Transaction - File Has Been " + "Changed Since You Inquired"); p.put("015", "Insufficient Information given to Search for the " + "File Requested"); p.put("01B", "Your NM Line Is Invalid"); p.put("01C", "Your Address Line Is Invalid"); p.put("01D", "Current and Former Address Both Out of Area"); p.put("01E", "Error in Employment Line"); p.put("021", "Error in Preparing Your Form - Re-Order Form - " + "Billing and Maintenance is OK"); p.put("02B", "City or State Is Misspelled or Not In ACROPAC System"); p.put("02C", "City or State Is Misspelled or Not In ACROPAC System"); p.put("02D", "City or State Misspelled or Not in ACROPAC System"); p.put("02E", "This Area of Records Temporarily Unavailable - " + "Please Try Later"); p.put("031", "Program Error - Print Total Transaction and " + "Notify Automation Division"); p.put("036", "Error in Preparing Your Form-Reorder Form Billing and " + "Maintenance is OK"); p.put("037", "Error in Preparing Your Form-Reorder Form Billing and " + "Maintenance is OK"); p.put("044", "Program Error on your Inquiry Notify Bureau rep and " + "try again later"); p.put("045", "Program Error - Print Total Transaction and Notify " + "Automation Division"); p.put("046", "City or Province is Misspelled or Not in ACROPAC System"); p.put("047", "Unable to prepare form Print Transaction and Notify " + "Automation Division"); p.put("048", "Program Error - Print Transaction and Notify Automation " + "Division"); p.put("049", "Program Error - Print Transaction and Notify Automation " + "Division"); p.put("04A", "Invalid Security Code Use Your Correct Code"); p.put("04D", "File Combine not Allowed on this Record Please " + "Notify Your Supervisor"); p.put("04E", "File Combine not Allowed on this Record Please " + "Notify Your Supervisor"); p.put("04F", "File Combine not Allowed on this Record Please " + "Notify Your Supervisor"); p.put("050", "File Combine not Allowed on this Record Please " + "Notify Your Supervisor"); p.put("051", "No Record Found"); p.put("06B", "Form Too Large too Fit in Queue Notify Systems Division"); p.put("065", "Run Initque Due to PRINT/FWS/F250"); p.put("067", "Exceeded Form Request Allowed for Oper-Form"); p.put("076", "Contact Bureau Supervisor - File Exceeds Maximum Length"); p.put("077", "Cancelled Member Number Contact Credit Bureau Sales " + "Department"); p.put("078", "Security Violation Unauthorized Transaction Type"); p.put("079", "Unable to Build Summary Output for This Record"); p.put("07A", "Unable to Build Full File Output for This Record"); p.put("07B", "Unable to Build Print Image Output for This Record"); p.put("092", "City Not Part of the Equifax Automated Network"); p.put("0BB", "Card Alert is Not Available or Had an Internal Error"); p.put("0BD", "No Deposit Alert Exchange Record Found"); this.processingErrors = p; } /** * Validity Error Map. */ private void createValidityErrorMap() { Map<String, String> v = new HashMap<>(); v.put("101", "Invalid Member Number"); v.put("102", "Invalid Teleprocessing Type"); v.put("103", "Invalid Company Code"); v.put("104", "Invalid Input/Output Code"); v.put("105", "Invalid Telephone Number Supplied"); v.put("106", "Invalid Business Telephone Number Supplied"); v.put("107", "Invalid Customer Code for Card Alert"); v.put("1F1", "Line One Duplicate Lines"); v.put("1F2", "Line One Unacceptable Foreign Bureau Line"); v.put("1F3", "Line One Invalid Line Mnemonic"); v.put("1F4", "Line One Longer Than 80 Bytes"); v.put("201", "Invalid Last Name"); v.put("202", "Invalid First Name"); v.put("203", "Invalid Middle Name"); v.put("204", "Invalid Suffix"); v.put("205", "Invalid Spouse Name"); v.put("2F1", "Line Two Duplicate Lines"); v.put("2F2", "Line Two Unacceptable Foreign Bureau Line"); v.put("2F3", "Line Two Invalid Line Mnemonic"); v.put("2F4", "Line Two Longer Than 80 Bytes"); v.put("401", "Invalid Subject Social Insurance Number"); v.put("402", "Invalid Subject Birth Date"); v.put("403", "Invalid Subject Age"); v.put("404", "***This Code Presently Not Used***"); v.put("405", "Invalid Spouse Birth Date"); v.put("406", "Invalid Spouse Social Insurance Number"); v.put("407", "Invalid Number of Dependents"); v.put("408", "Invalid Marital Status"); v.put("4F1", "Line Four Duplicate Lines"); v.put("4F2", "Line Four Unacceptable Foreign Bureau Line"); v.put("4F3", "Line Four Invalid Line Mnemonic"); v.put("4F4", "Line Four Longer Than 80 Bytes"); v.put("601", "Invalid Occupation"); v.put("602", "Invalid Name of Employer"); v.put("701", "Invalid Account Number Mnemonic Entered"); v.put("702", "Invalid Account Number Reference Entered"); v.put("7F1", "Line Seven Duplicate Lines"); v.put("7F2", "Line Seven Unacceptable Foreign Bureau Line"); v.put("7F3", "Line Seven Invalid Line Mnemonic"); v.put("7F4", "Line Seven Longer Than 80 Bytes"); v.put("301", "Invalid Street Number"); v.put("302", "Invalid Street Name"); v.put("303", "***This Code Presently Not Used***"); v.put("304", "Invalid City Name"); v.put("305", "Invalid Province Code"); v.put("306", "Invalid Postal Code"); v.put("307", "Invalid Date Since"); v.put("308", "Invalid R-O-B Code"); v.put("3F1", "Line Three Duplicate Lines"); v.put("3F2", "Line Three Unacceptable Foreign Bureau Line"); v.put("3F3", "Line Three Invalid Line Mnemonic"); v.put("3F4", "Line Three Longer Than 80 Bytes"); v.put("501", "Invalid Former Address Street Number"); v.put("502", "Invalid Former Address Street Name"); v.put("503", "***This Code Presently Not Used***"); v.put("504", "Invalid Former Address City Name"); v.put("505", "Invalid Former Address Province Code"); v.put("506", "Invalid Former Address Postal Code"); v.put("507", "Invalid Former Address Date Since"); v.put("508", "Invalid Former Address R-O-B Code"); v.put("5F1", "Line Five Duplicate Lines"); v.put("5F2", "Line Five Unacceptable Foreign Bureau Line"); v.put("5F3", "Line Five Invalid Line Mnemonic"); v.put("5F4", "Line Five Longer Than 80 Bytes"); this.validityErrors = v; } /** * Process SCOR segment and update Response. * @param scorSegment {@link ScorSegment} * @param form {@link FormJSON} * @return {@link String} error message */ private Map<String, List<FormJSONValidator>> processScorSegment(final ScorSegment scorSegment, final FormJSON form) { final int group = 3; Map<String, List<FormJSONValidator>> map = new HashMap<>(); String narrative = scorSegment.getDecisionNarrative(); Matcher matcher = UNDER_PATTERN.matcher(narrative); if (matcher.matches()) { try { String s = matcher.group(group).trim(); String[] strs = s.split("[ \\$]"); int max = Integer.parseInt(strs[1]); FormJSONValidator v1 = new FormJSONValidator(); v1.setRule(FormJSONValidatorRuleType.MAX); v1.setValue(strs[1]); v1.setMessage("Approved for a maximum of $" + this.formatter.format(max)); FormJSONValidator v2 = new FormJSONValidator(); v2.setRule(FormJSONValidatorRuleType.MESSAGE); v2.setMessage("Approved for a maximum of $" + this.formatter.format(max)); // SubTotal // TODO remove & change to something that can be references by // other forms map.put("$st", Arrays.asList(v1)); map.put("*", Arrays.asList(v2)); Locale locale = new Locale("en", "US"); NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale); String money = currencyFormatter.format(max); if (money.endsWith(".00")) { int centsIndex = money.lastIndexOf(".00"); if (centsIndex != -1) { money = money.substring(1, centsIndex); } } findValueByKey(form, "approvedamount").get().setValue(money); } catch (NumberFormatException e) { addValidation(map, FormJSONValidatorRuleType.STOP, "Unable to process Equifax Request. " + "Please try again later."); } } else { addValidation(map, FormJSONValidatorRuleType.STOP, "Application is not approved"); } return map; } /** * Add Stop Workflow Validation. * @param map {@link Map} * @param rule {@link FormJSONValidatorRuleType} * @param message {@link String} */ private void addValidation(final Map<String, List<FormJSONValidator>> map, final FormJSONValidatorRuleType rule, final String message) { FormJSONValidator v = new FormJSONValidator(); v.setRule(rule); v.setMessage(message); map.put("*", Arrays.asList(v)); } /** * Find the Scor Segment. * @param factory {@link SegmentFactory} * @param response {@link String} * @return {@link Pair} */ private Pair<ScorSegment, CERRSegment> findScorSegment(final SegmentFactory factory, final String response) { CERRSegment err = null; ScorSegment scor = null; try { List<Segment> segments = factory.readSegments(response); scor = (ScorSegment) findSegment(segments, ScorSegment.class); err = (CERRSegment) findSegment(segments, CERRSegment.class); } catch (UnidentifiedRecordException e) { err = null; scor = findScorSegmentSecondCheck(factory, response); } return Pair.of(scor, err); } /** * Finds the segment. * @param segments List<Segment> * @param clazz Class * @return ScorSegment */ private Segment findSegment(final List<Segment> segments, final Class<?> clazz) { for (Segment segment : segments) { if (segment.getClass().equals(clazz)) { return segment; } } return null; } /** * Secondary Check to find SCOR segment in string. * @param factory {@link SegmentFactory} * @param responseData {@link String} * @return {@link ScorSegment} */ private ScorSegment findScorSegmentSecondCheck(final SegmentFactory factory, final String responseData) { ScorSegment scor = null; try { if (!StringUtils.isEmpty(responseData)) { int pos = responseData.indexOf("##SCOR"); if (pos > -1) { pos += 2; String substring = responseData.substring(pos, pos + ScorSegment.LENGTH); List<Segment> segments = factory.readSegments(substring); scor = (ScorSegment) findSegment(segments, ScorSegment.class); } } } catch (Exception e) { scor = null; } return scor; } /** * Determine error code. * @param err {@link CERRSegment} * @return {@link String} */ private String getErrorCode(final CERRSegment err) { if (err.getNumberOfProcessingErrors() > 0) { String code = this.processingErrors.get(err.getProcessingErrorCode1()); if (StringUtils.isEmpty(code)) { code = "Application is not approved PE " + err.getProcessingErrorCode1(); } return code; } if (err.getNumberOfFormatErrors() > 0) { String code = this.formatErrors.get(err.getFormatErrorCode1()); if (StringUtils.isEmpty(code)) { code = "Application is not approved FE " + err.getFormatErrorCode1(); } return code; } if (err.getNumberOfValidityErrors() > 0) { String code = this.validityErrors.get(err.getValidityErrorCode1()); if (StringUtils.isEmpty(code)) { code = "Application is not approved VC " + err.getValidityErrorCode1(); } return code; } return "Application is not approved"; } }