/*
jBilling - The Enterprise Open Source Billing System
Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde
This file is part of jbilling.
jbilling is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jbilling is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with jbilling. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sapienter.jbilling.server.pluggableTask;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.sapienter.jbilling.server.payment.PaymentDTOEx;
import com.sapienter.jbilling.server.payment.db.PaymentAuthorizationDTO;
import com.sapienter.jbilling.server.payment.db.PaymentResultDAS;
import com.sapienter.jbilling.server.pluggableTask.admin.ParameterDescription;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskDTO;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException;
import com.sapienter.jbilling.server.user.db.AchDTO;
import com.sapienter.jbilling.server.user.db.CreditCardDTO;
import com.sapienter.jbilling.server.util.Constants;
/**
* Fake payment processor, providing the ability to configure a jbilling
* installation for testing, where credit card transactions are not processed by
* a real payment processor.
*
* Behaviour of this processor completely depends on the actual credit card
* number. In particlular:
*
* <ul>
* <li>If the number ends with an even number, it will always result on a
* successful payment</li>
* <li>If the number ends with an odd number it will always result on a failed
* payment</li>
* <li>If the number ends with "0" or with any not number symbol, then the
* result will be 'unavailable'.</li>
* </ul>
*/
public class PaymentFakeTask extends PaymentTaskBase implements PaymentTask {
// pluggable task parameters names
public static final ParameterDescription PARAM_PROCESSOR_NAME_OPTIONAL =
new ParameterDescription("processor_name", false, ParameterDescription.Type.STR);
public static final ParameterDescription PARAM_CODE1_OPTIONAL =
new ParameterDescription("code", false, ParameterDescription.Type.STR);
public static final ParameterDescription PARAM_HANDLE_ALL_REQUESTS =
new ParameterDescription("all", false, ParameterDescription.Type.STR);
public static final ParameterDescription PARAM_NAME_PREFIX =
new ParameterDescription("name_prefix", false, ParameterDescription.Type.STR);
public static final ParameterDescription PARAM_ACCEPT_ACH =
new ParameterDescription("accept-ach", false, ParameterDescription.Type.STR);
public static final String VALUE_PROCESSOR_NAME_DEFAULT = "fake-processor";
public static final String VALUE_CODE1_DEFAULT = "fake-code-default";
private static final String PREAUTH_TRANSACTION_PREFIX = "pAuth-";
//initializer for pluggable params
{
descriptions.add(PARAM_ACCEPT_ACH);
descriptions.add(PARAM_CODE1_OPTIONAL);
descriptions.add(PARAM_HANDLE_ALL_REQUESTS);
descriptions.add(PARAM_NAME_PREFIX);
descriptions.add(PARAM_PROCESSOR_NAME_OPTIONAL);
}
private boolean myShouldBlockOtherProcessors;
private boolean acceptAch;
private Filter myFilter = Filter.ACCEPT_ALL;
private static final Logger LOG = Logger.getLogger(PaymentFakeTask.class);
public void failure(Integer userId, Integer retry) {
// nothing to do -- ageing process would probably started by real
// implementation
}
@Override
public void initializeParamters(PluggableTaskDTO task) throws PluggableTaskException {
super.initializeParamters(task);
myShouldBlockOtherProcessors = Boolean.parseBoolean((String) parameters.get(PARAM_HANDLE_ALL_REQUESTS.getName()));
acceptAch = Boolean.parseBoolean((String)parameters.get(PARAM_ACCEPT_ACH.getName()));
myFilter = Filter.ACCEPT_ALL;
if (!myShouldBlockOtherProcessors) {
myFilter = createFilter((String) parameters.get(PARAM_NAME_PREFIX.getName()));
}
}
public boolean process(PaymentDTOEx paymentInfo) throws PluggableTaskException {
LOG.debug("processing " + paymentInfo + " cc number is " + paymentInfo.getCreditCard().getNumber());
Result result = doFakeAuthorization(paymentInfo, null);
LOG.debug("result " + result);
return result.shouldCallOtherProcessors();
}
public boolean preAuth(PaymentDTOEx paymentInfo)
throws PluggableTaskException {
LOG.debug("preAuth payment " + paymentInfo);
String transactionId = generatePreAuthTransactionId();
Result result = doFakeAuthorization(paymentInfo, transactionId);
LOG.debug("result " + result);
return result.shouldCallOtherProcessors();
}
public boolean confirmPreAuth(PaymentAuthorizationDTO auth, PaymentDTOEx paymentInfo)
throws PluggableTaskException {
LOG.debug("confirmPreAuth" + auth + " payment " + paymentInfo);
if (!getFakeProcessorName().equals(auth.getProcessor())) {
LOG.warn("name of processor does not match " + getFakeProcessorName() +
" " + auth.getProcessor());
}
if (!isPreAuthTransactionId(auth.getTransactionId())) {
LOG.warn("AuthorizationDTOEx with transaction id: " + auth.getTransactionId() + " is used as preauth data");
}
Result result = doFakeAuthorization(paymentInfo, null);
LOG.debug("returning " + result);
return result.shouldCallOtherProcessors();
}
private Result doFakeAuthorization(PaymentDTOEx payment, String transactionId) throws PluggableTaskException {
CreditCardDTO creditCard = payment.getCreditCard();
AchDTO ach = payment.getAch();
boolean isAch = false;
if (creditCard == null || !myFilter.accept(creditCard)) {
//give real processors a chance
if (!acceptAch || ach == null) {
return new Result(null, true);
}
isAch = true;
}
Integer resultId;
if (!isAch) {
resultId = getProcessResultId(creditCard);
} else {
String val = payment.getAmount().toPlainString();
resultId = (Integer.parseInt(val.substring(val.length() - 1)) % 2 == 0) ?
Constants.RESULT_OK : Constants.RESULT_FAIL;
}
payment.setPaymentResult(new PaymentResultDAS().find(resultId));
PaymentAuthorizationDTO authInfo = createAuthorizationDTO(resultId, transactionId);
storeProcessedAuthorization(payment, authInfo);
boolean wasProcessed = (Constants.RESULT_FAIL.equals(resultId) || Constants.RESULT_OK.equals(resultId));
boolean shouldCallOthers = !wasProcessed && !myShouldBlockOtherProcessors;
return new Result(authInfo, shouldCallOthers);
}
private String generatePreAuthTransactionId() {
String retValue = PREAUTH_TRANSACTION_PREFIX + System.currentTimeMillis();
if (retValue.length() > 20) {
return retValue.substring(0, 20);
}
return retValue;
}
private boolean isPreAuthTransactionId(String transactionId) {
return transactionId != null && transactionId.startsWith(PREAUTH_TRANSACTION_PREFIX);
}
private Integer getProcessResultId(CreditCardDTO card) {
String cardNumber = card.getNumber();
char last = (cardNumber.length() == 0) ? ' ' : cardNumber.charAt(cardNumber.length() - 1);
switch (last) {
case '2':
case '4':
case '6':
case '8':
return Constants.RESULT_OK;
case '1':
case '3':
case '5':
case '7':
case '9':
return Constants.RESULT_FAIL;
default:
return Constants.RESULT_UNAVAILABLE;
}
}
private PaymentAuthorizationDTO createAuthorizationDTO(Integer resultConstant, String transactionId) {
return createAuthorizationDTO(Constants.RESULT_OK.equals(resultConstant), transactionId);
}
private PaymentAuthorizationDTO createAuthorizationDTO(boolean isAuthorized, String transactionId) {
PaymentAuthorizationDTO auth = new PaymentAuthorizationDTO();
auth.setProcessor(getFakeProcessorName());
auth.setCode1(getFakeCode1());
auth.setTransactionId(transactionId);
auth.setResponseMessage(isAuthorized ? "The transaction has been approved" : "Transaction failed");
return auth;
}
private String getFakeProcessorName() {
String result = (String) parameters.get(PARAM_PROCESSOR_NAME_OPTIONAL.getName());
if (result == null) {
result = VALUE_PROCESSOR_NAME_DEFAULT;
}
return result;
}
private String getFakeCode1() {
String result = (String) parameters.get(PARAM_CODE1_OPTIONAL.getName());
if (result == null) {
result = VALUE_CODE1_DEFAULT;
}
return result;
}
@Override
public String toString() {
return "PaymentFakeTask: " + System.identityHashCode(this) +
", blocking: " + myShouldBlockOtherProcessors +
", filter: " + myFilter.toString() +
", code1: " + getFakeCode1();
}
private static Filter createFilter(String prefix) {
return (prefix == null || prefix.trim().length() == 0) ? Filter.ACCEPT_ALL : new NameStartsWithFilter(prefix);
}
/**
* Selects requests that should be processed by fake implementation
*/
private static interface Filter {
public boolean accept(CreditCardDTO card);
public static Filter ACCEPT_ALL = new Filter() {
public boolean accept(CreditCardDTO card) {
return true;
}
@Override
public String toString() {
return "Filter#ACCEPT_ALL";
}
};
}
private static class NameStartsWithFilter implements Filter {
private final String myPrefix;
public NameStartsWithFilter(String prefix) {
myPrefix = prefix;
}
public boolean accept(CreditCardDTO card) {
String name = card.getName();
return name != null && name.startsWith(myPrefix);
}
@Override
public String toString() {
return "Filter#startsWith:" + myPrefix;
}
}
}