Index: branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/tams/util/PasClient.java =================================================================== diff -u --- branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/tams/util/PasClient.java (revision 0) +++ branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/tams/util/PasClient.java (revision 256544) @@ -0,0 +1,139 @@ +package com.gpc.tams.util; + +import com.gpc.tams.json.CheckPinpadResp; +import com.gpc.tams.json.SaleResp; +import com.gpc.tams.json.SaleErrorResp; +import com.gpc.tams.json.SaleReq; +import org.apache.commons.lang.ArrayUtils; +import org.apache.log4j.Logger; +import org.codehaus.jackson.map.ObjectMapper; +import org.springframework.http.*; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.HttpClientErrorException; + +import java.io.IOException; +import java.util.Arrays; + +@Service +public class PasClient { + private static final ObjectMapper mapper = new ObjectMapper(); + public static final String PAYMENT_URL = "http://localhost:14447/v1"; + public static final String SALE_ENDPOINT = "/transaction/sale"; + public static final String CHECK_PINPAD_ENDPOINT = "/pinpad"; + public static final String REVERSAL_ENDPOINT = "/transaction/reversal"; + public static final String REFUND_ENDPOINT = "/transaction/refund"; + public static final String BALANCE_ENDPOINT = "/transaction/inquiry"; + public static final String SIGNATURE_ENDPOINT = "/signature"; + public static final String SUBNET_PREFIX = "172.16.1"; + public static final String REQ_BODY = "Request Body: "; + public static final String RESP_BODY = "Response Body: "; + public static final String URL = "URL: "; + public static final String X_COORELATION_ID = "x-correlation-id"; + public static final String CURRENCY_USD = "USD"; + + public static final String NO_IMPLEMENTATION = "There is no implementation for the NAPA Payment - "; + public static final String ERROR_CODE_1000 = "val-1000"; + public static final String ERROR_CODE_1001 = "val-1001"; + public static final String ERROR_CODE_1002 = "val-1002"; + public static final String ERROR_CODE_9000 = "val-9000"; + public static final String ERROR_CODE_9001 = "val-9001"; + + private Logger logger; + + RestTemplate restTemplate; + + + public PasClient() { + restTemplate = new RestTemplate(); + MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter(); + converter.setObjectMapper(new ObjectMapper()); + restTemplate.getMessageConverters().add(converter); + } + + public ResponseEntity post(String request, String url, String uuid) { + log(REQ_BODY + request); + log(URL + url); + log(X_COORELATION_ID + uuid); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + headers.add(X_COORELATION_ID, uuid); + HttpEntity entity = new HttpEntity(request, headers); + try { + ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); + String resp = response.getBody(); + log(RESP_BODY + resp); + return response; + } catch (Exception e) { + logError("post failed with status code", e); + throw e; + } + } + + public String get(String url, String uuid) { + log(URL + url); + log(X_COORELATION_ID + uuid); + HttpHeaders headers = new HttpHeaders(); + headers.add(X_COORELATION_ID, uuid); + HttpEntity entity = new HttpEntity(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + String resp = response.getBody(); + log(RESP_BODY + resp); + return resp; + } + + public void setLogger(Logger logger) { + this.logger = logger; + } + + public static boolean is2xxSuccessful(HttpStatus status) { + int[] successfulCodes = {200, 201, 202}; + return ArrayUtils.contains(successfulCodes, status.value()); + } + + public static boolean isDisallowed(RefTenderType refTenderType, Integer[] disallowedRefTenderTypes) { + if (ArrayUtils.isEmpty( disallowedRefTenderTypes )) { return false; } + + if (refTenderType == null) { + return true; + } + + // there are two tender types - the general one - Credit/Debit/Gift + // and the more specific ones: + // + // Wright Express (credit/fleet card) + // Voyager (credit/fleet card) + // American Express (credit/amex) + // Mastercard (credit (or debit)/mastercard) + // Visa (credit (or debit)/visa) + // Discover (credit/discover) + // + // The general tender type is returned explicitly in the PX message, but the more specific one isn't + // always obvious. + // + + return (Arrays.asList(disallowedRefTenderTypes).contains(new Integer(refTenderType.getRefTenderTypeId()))); + + } + + private void log(String mesg) { + if (logger != null) { + logger.info(mesg); + } + } + + private void logError(String mesg) { + if (logger != null) { + logger.error(mesg); + } + } + + private void logError(String mesg, Exception e) { + if (logger != null) { + logger.error(mesg, e); + } + } + +} Index: branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/manager/PasManager.java =================================================================== diff -u --- branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/manager/PasManager.java (revision 0) +++ branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/manager/PasManager.java (revision 256544) @@ -0,0 +1,50 @@ +package com.gpc.manager; + +import com.gpc.service.IPC_Context; +import com.gpc.tams.json.Store; +import com.gpc.tams.repository.common.StoreProfile2; +import com.gpc.tams.repository.common.StoreProfileDAO2; +import com.gpc.tams.repository.ipc.IpcTransactionTracker; +import com.gpc.tams.repository.salesactivities.InvoicingProfile2; +import com.gpc.tams.repository.salesactivities.InvoicingProfileDAO2; +import com.gpc.tams.util.ExecutionContext; +import com.gpc.tams.util.PasClient; +import org.codehaus.jackson.map.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +abstract public class PasManager { + + @Autowired + protected ExecutionContext context; + + @Autowired + protected IpcTransactionTracker txTracker; + + @Autowired + protected PasClient client; + + @Autowired + protected StoreProfileDAO2 storeProfileDAO; + + @Autowired + protected InvoicingProfileDAO2 invoicingProfileDAO; + + static final ObjectMapper mapper = new ObjectMapper(); + + protected InvoicingProfile2 getInvoicingProfile(int location) { + InvoicingProfile2 invProfile = invoicingProfileDAO.find(location); + String baseurl = invProfile.getPasIpcUrl() != null ? invProfile.getPasIpcUrl() : PasClient.PAYMENT_URL; + invProfile.setIpcBaseUrl(baseurl); + return invProfile; + } + + protected void setStore(Store store) { + StoreProfile2 storeProfile = storeProfileDAO.find(); + store.setType(storeProfile.getStoreType().getCodeString()); + store.setGpcOwned(storeProfile.getGpcOwned()==1); + store.setCity(storeProfile.getCity()); + store.setPostalCode(storeProfile.getPostalCode()); + } +} Index: branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/manager/PasSaleManager.java =================================================================== diff -u --- branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/manager/PasSaleManager.java (revision 0) +++ branches/b_4_15_00_00/IntegratedPaymentCard/src/main/java/com/gpc/manager/PasSaleManager.java (revision 256544) @@ -0,0 +1,155 @@ +package com.gpc.manager; + +import com.gpc.dto.SaleResponseDetail; +import com.gpc.message.isd.fields.CardType; +import com.gpc.message.isd.fields.StatusCode; +import com.gpc.tams.json.*; +import com.gpc.tams.repository.ipc.IpcTransactionLog; +import com.gpc.tams.repository.ipc.TransactionType; +import com.gpc.tams.util.RefTenderType; +import com.gpc.tams.util.PasClient; + +import java.math.BigDecimal; +import java.sql.Timestamp; + +import org.apache.commons.lang.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; + + +@Service("pasSaleManager") +public class PasSaleManager extends PasManager { + + @Autowired + private PASReversalSaleManager voidSaleManager; + + protected String endpoint = PasClient.SALE_ENDPOINT; + protected TransactionType transactionType = TransactionType.SALE; + + public SaleResponseDetail processTransaction( String journalKey, + int location, + int storeNumber, + int terminalNumber, + int employeeId, + BigDecimal amount, + boolean bypassSwipe, + Integer[] disallowedRefTenderTypes + ) { + + context.logDebug("journalKey: %s, storeNumber: %s, terminalNumber: %s, amount: %s, bypass: %s, disallowedTenderTypes: %s", + journalKey, storeNumber, terminalNumber, amount, bypassSwipe, ArrayUtils.toString(disallowedRefTenderTypes)); + + SaleResponseDetail response = null; + if (storeNumber == 9999) { + context.logWarn("Using Mock response instead of normal response"); + response = new MockSaleResponseBuilder(amount, bypassSwipe, disallowedRefTenderTypes, journalKey, storeNumber, terminalNumber).generateResponse(); + context.logWarn("Mock Response: "+response.toString()); + return response; + } + + final IpcTransactionLog txLog = txTracker.startTracking( journalKey, storeNumber, terminalNumber, employeeId, transactionType, amount ); + + SaleReq saleReq = createSaleReq(journalKey, location, storeNumber, terminalNumber, employeeId, amount); + try { + String url = getInvoicingProfile(location).getIpcBaseUrl() + endpoint; + setStore(saleReq.getStore()); + + String saleReqStr = mapper.writeValueAsString(saleReq); + ResponseEntity entity = client.post(saleReqStr, url, journalKey); + SaleResp saleResp = mapper.readValue(entity.getBody(), SaleResp.class); + + // TODO need to update CardType if PAS has new type + CardType brand = CardType.get(saleResp.getCard().getBrand()); + RefTenderType refTenderType = brand.getTenderType(); + int refTenderTypeId = refTenderType==null?-1:refTenderType.getRefTenderTypeId(); + + response = new SaleResponseDetail( + storeNumber, + terminalNumber, + journalKey, + StatusCode.UNKNOWN_EXCEPTION.getCode(), + saleResp.getProcessor().getResponseMessage(), + amount, + saleResp.getTimestamp(), + saleResp.getProcessor().getAuthorizationCode(), + String.valueOf(saleResp.getProcessor().getResponseCode()), + null, //authMsg.getHostActionCode(), + null, //authMsg.getExpirationDate(), + null, //authMsg.getCashBack(), + // use the account number to determine what type of card this actually is + // and make sure its consistent with the tender type. + brand, + refTenderTypeId, + true, + null, + saleResp.getCard().getLastFour(), //authMsg.getPartialAccountNumber(), + null); //authMsg.getCardHolderName() + if (saleResp.getStatus().equals(StatusCode.APPROVED.toString())) { + // TODO remove this code when disallowedRefTenderTypes is passed to PAS + if (PasClient.isDisallowed(refTenderType, disallowedRefTenderTypes)) { + response = new SaleResponseDetail(storeNumber, terminalNumber, journalKey, StatusCode.LOOKUP_FAILED.getCode(), "Card Type Not Allowed"); + + if (refTenderType == null) { + context.logWarn("Unknown tender type, using: "+RefTenderType.CASH); + response.setRefTenderTypeId(RefTenderType.CASH.getRefTenderTypeId()); + } else { + response.setRefTenderTypeId(refTenderTypeId); + } + // TODO add void code + } else { + TransactionLogHandler.updateTxLogEntry ( txLog, saleResp ); + // response = captureSignature(saleResp); + response.setStatusCode(StatusCode.APPROVED.getCode()); + } + } else { + response.setStatusCode(StatusCode.DECLINED.getCode()); + } + } catch (HttpClientErrorException | HttpServerErrorException e) { + try { + String saleRespStr = e.getResponseBodyAsString(); + SaleErrorResp error = mapper.readValue(saleRespStr, SaleErrorResp.class); + context.logError(e,"Sale failed:" + error.getCode() + " | " + error.getMessage() + " | " + error.getHelp()); + response = new SaleResponseDetail (storeNumber, terminalNumber, journalKey, StatusCode.PAV_UNKNOWN_EXCEPTION.getCode(), error.getMessage()); + } catch (Exception ex) { + context.logError(e,"Sale failed to parse failed response"); + response = new SaleResponseDetail( storeNumber, terminalNumber, journalKey, StatusCode.PAV_UNKNOWN_EXCEPTION.getCode(), e.getLocalizedMessage()); + } + } catch (Exception e) { + context.logError(e,"Sale failed"); + response = new SaleResponseDetail( storeNumber, terminalNumber, journalKey, StatusCode.PAV_UNKNOWN_EXCEPTION.getCode(), e.getLocalizedMessage()); + } finally { + txLog.setItkCallCompleteTime( new Timestamp( System.currentTimeMillis() ) ); + txLog.setItkResultStatus( response == null ? StatusCode.UNKNOWN_EXCEPTION.getCode() : response.getStatusCode() ); + txTracker.updateTracking( txLog ); + } + + return response; + } + + protected SaleResponseDetail captureSignature(SaleResp saleResp) { + // TODO add implementation + return null; + } + + private SaleReq createSaleReq(String journalKey, int location, int storeNumber, int terminalNumber, int employeeId, BigDecimal amount) { + SaleReq saleReq = new SaleReq(); + saleReq.setPinpadId(String.valueOf(terminalNumber)); + + SaleTransaction transaction = new SaleTransaction(); + saleReq.setTransaction(transaction); + transaction.setAmount(amount); + transaction.setStoreTransactionId(journalKey); + transaction.setCurrency(PasClient.CURRENCY_USD); + + Store store = new Store(); + saleReq.setStore(store); + store.setNumber(storeNumber); + store.setLocation(location); + store.setTerminalId(String.valueOf(terminalNumber)); + store.setEmployeeId(employeeId); + return saleReq; + } +}