/* * #%L * BroadleafCommerce Integration * %% * Copyright (C) 2009 - 2015 Broadleaf Commerce * %% * 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. * #L% */ package org.broadleafcommerce.core.payment.service; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.validator.CreditCardValidator; import org.broadleafcommerce.common.money.Money; import org.broadleafcommerce.common.payment.PaymentAdditionalFieldType; import org.broadleafcommerce.common.payment.PaymentDeclineType; import org.broadleafcommerce.common.payment.PaymentTransactionType; import org.broadleafcommerce.common.payment.PaymentType; import org.broadleafcommerce.common.payment.dto.CreditCardDTO; import org.broadleafcommerce.common.payment.dto.PaymentRequestDTO; import org.broadleafcommerce.common.payment.dto.PaymentResponseDTO; import org.broadleafcommerce.common.payment.service.FailureCountExposable; import org.broadleafcommerce.common.payment.service.PaymentGatewayTransactionService; import org.broadleafcommerce.common.vendor.service.exception.PaymentException; import org.broadleafcommerce.common.vendor.service.type.ServiceStatusType; import org.joda.time.DateTime; import org.springframework.stereotype.Service; /** * This is an example implementation of a {@link org.broadleafcommerce.common.payment.service.PaymentGatewayTransactionService}. * This handles the scenario where the implementation is PCI-Compliant and * the server directly handles the Credit Card PAN. If so, this service should make * a server to server call to charge the card against the configured gateway. * * In order to use load this demo service, you will need to component scan * the package "com.mycompany.sample". * * This should NOT be used in production, and is meant solely for demonstration * purposes only. * * @author Elbert Bautista (elbertbautista) */ @Service("blNullPaymentGatewayTransactionService") public class NullPaymentGatewayTransactionServiceImpl implements PaymentGatewayTransactionService, FailureCountExposable { @Override public PaymentResponseDTO authorize(PaymentRequestDTO paymentRequestDTO) throws PaymentException { return commonCreditCardProcessing(paymentRequestDTO, PaymentTransactionType.AUTHORIZE); } /** * for the test implementation, and in order to test different failed response scenarios, check for the presence of a "desired outcome" * entry in the request's additional fields */ @Override public PaymentResponseDTO capture(PaymentRequestDTO paymentRequestDTO) throws PaymentException { PaymentResponseDTO responseDTO = new PaymentResponseDTO(PaymentType.THIRD_PARTY_ACCOUNT, NullPaymentGatewayType.NULL_GATEWAY); responseDTO.paymentTransactionType(PaymentTransactionType.AUTHORIZE_AND_CAPTURE); responseDTO.amount(new Money(paymentRequestDTO.getTransactionTotal())); Map<String, Object> additionalFields = paymentRequestDTO.getAdditionalFields(); if (additionalFields != null) { if (additionalFields.containsKey("desiredOutcome")) { String desiredOutome = (String) additionalFields.get("desiredOutcome"); if (desiredOutome.equals("SOFT DECLINE")) { responseDTO.successful(false); responseDTO.rawResponse("confirmation - failure - soft decline"); responseDTO.responseMap(PaymentAdditionalFieldType.DECLINE_TYPE.getType(), PaymentDeclineType.SOFT.getType()); } else if (desiredOutome.equals("HARD DECLINE")) { responseDTO.successful(false); responseDTO.rawResponse("confirmation - failure - hard decline"); responseDTO.responseMap(PaymentAdditionalFieldType.DECLINE_TYPE.getType(), PaymentDeclineType.HARD.getType()); } } } else { responseDTO.rawResponse("confirmation - success"); responseDTO.successful(true); } return responseDTO; } @Override public PaymentResponseDTO authorizeAndCapture(PaymentRequestDTO paymentRequestDTO) throws PaymentException { return commonCreditCardProcessing(paymentRequestDTO, PaymentTransactionType.AUTHORIZE_AND_CAPTURE); } @Override public PaymentResponseDTO reverseAuthorize(PaymentRequestDTO paymentRequestDTO) throws PaymentException { throw new PaymentException("The Rollback authorize method is not supported for this module"); } @Override public PaymentResponseDTO refund(PaymentRequestDTO paymentRequestDTO) throws PaymentException { PaymentResponseDTO responseDTO = new PaymentResponseDTO(PaymentType.CREDIT_CARD, NullPaymentGatewayType.NULL_GATEWAY); responseDTO.valid(true) .paymentTransactionType(PaymentTransactionType.REFUND) .amount(new Money(paymentRequestDTO.getTransactionTotal())) .rawResponse("Successful Refund") .successful(true); return responseDTO; } @Override public PaymentResponseDTO voidPayment(PaymentRequestDTO paymentRequestDTO) throws PaymentException { throw new PaymentException("The void method is not supported for this module"); } /** * Does minimal Credit Card Validation (luhn check and expiration date is after today). * Mimics the Response of a real Payment Gateway. * * @param creditCardDTO * @return */ protected PaymentResponseDTO commonCreditCardProcessing(PaymentRequestDTO requestDTO, PaymentTransactionType paymentTransactionType) { PaymentResponseDTO responseDTO = new PaymentResponseDTO(PaymentType.CREDIT_CARD, NullPaymentGatewayType.NULL_GATEWAY); responseDTO.valid(true) .paymentTransactionType(paymentTransactionType); CreditCardDTO creditCardDTO = requestDTO.getCreditCard(); String transactionAmount = requestDTO.getTransactionTotal(); CreditCardValidator visaValidator = new CreditCardValidator(CreditCardValidator.VISA); CreditCardValidator amexValidator = new CreditCardValidator(CreditCardValidator.AMEX); CreditCardValidator mcValidator = new CreditCardValidator(CreditCardValidator.MASTERCARD); CreditCardValidator discoverValidator = new CreditCardValidator(CreditCardValidator.DISCOVER); if (StringUtils.isNotBlank(transactionAmount) && StringUtils.isNotBlank(creditCardDTO.getCreditCardNum()) && (StringUtils.isNotBlank(creditCardDTO.getCreditCardExpDate()) || (StringUtils.isNotBlank(creditCardDTO.getCreditCardExpMonth()) && StringUtils.isNotBlank(creditCardDTO.getCreditCardExpYear())))) { boolean validCard = false; if (visaValidator.isValid(creditCardDTO.getCreditCardNum())) { validCard = true; } else if (amexValidator.isValid(creditCardDTO.getCreditCardNum())) { validCard = true; } else if (mcValidator.isValid(creditCardDTO.getCreditCardNum())) { validCard = true; } else if (discoverValidator.isValid(creditCardDTO.getCreditCardNum())) { validCard = true; } boolean validDateFormat = false; boolean validDate = false; String[] parsedDate = null; if (StringUtils.isNotBlank(creditCardDTO.getCreditCardExpDate())) { parsedDate = creditCardDTO.getCreditCardExpDate().split("/"); } else { parsedDate = new String[2]; parsedDate[0] = creditCardDTO.getCreditCardExpMonth(); parsedDate[1] = creditCardDTO.getCreditCardExpYear(); } if (parsedDate.length == 2) { String expMonth = parsedDate[0]; String expYear = parsedDate[1]; try { DateTime expirationDate = new DateTime(Integer.parseInt("20" + expYear), Integer.parseInt(expMonth), 1, 0, 0); expirationDate = expirationDate.dayOfMonth().withMaximumValue(); validDate = expirationDate.isAfterNow(); validDateFormat = true; } catch (Exception e) { //invalid date format } } if (!validDate || !validDateFormat) { responseDTO.amount(new Money(0)) .rawResponse("cart.payment.expiration.invalid") .successful(false); } else if (!validCard) { responseDTO.amount(new Money(0)) .rawResponse("cart.payment.card.invalid") .successful(false); } else { responseDTO.amount(new Money(requestDTO.getTransactionTotal())) .rawResponse("Success!") .successful(true); } } else { responseDTO.amount(new Money(0)) .rawResponse("cart.payment.invalid") .successful(false); } return responseDTO; } protected Integer failureCount = 0; protected Boolean isUp = true; public synchronized void clearStatus() { isUp = true; failureCount = 0; } /** * arbitrarily set a failure threshold value of "3" */ public synchronized void incrementFailure() { if (failureCount >= getFailureReportingThreshold()) { isUp = false; } else { failureCount++; } } @Override public synchronized ServiceStatusType getServiceStatus() { if (isUp) { return ServiceStatusType.UP; } else { return ServiceStatusType.DOWN; } } @Override public Integer getFailureReportingThreshold() { return 3; } }