/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.webservices.utils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.accounts.AccountOwner;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.fees.transaction.TransactionFee;
import nl.strohalm.cyclos.entities.accounts.transactions.PaymentRequestTicket;
import nl.strohalm.cyclos.entities.accounts.transactions.Transfer;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery;
import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomField;
import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomFieldValue;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.services.ServiceClient;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.services.access.exceptions.BlockedCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.InvalidCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.InvalidUserForChannelException;
import nl.strohalm.cyclos.services.access.exceptions.UserNotFoundException;
import nl.strohalm.cyclos.services.accounts.AccountServiceLocal;
import nl.strohalm.cyclos.services.customization.PaymentCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.elements.MemberServiceLocal;
import nl.strohalm.cyclos.services.services.ServiceClientServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.services.transactions.DoPaymentDTO;
import nl.strohalm.cyclos.services.transactions.TransactionContext;
import nl.strohalm.cyclos.services.transactions.exceptions.MaxAmountPerDayExceededException;
import nl.strohalm.cyclos.services.transactions.exceptions.NotEnoughCreditsException;
import nl.strohalm.cyclos.services.transactions.exceptions.UpperCreditLimitReachedException;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeServiceLocal;
import nl.strohalm.cyclos.utils.CustomFieldHelper;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.webservices.WebServiceContext;
import nl.strohalm.cyclos.webservices.model.FieldValueVO;
import nl.strohalm.cyclos.webservices.model.TransactionFeeVO;
import nl.strohalm.cyclos.webservices.payments.AbstractPaymentParameters;
import nl.strohalm.cyclos.webservices.payments.AccountHistoryParams;
import nl.strohalm.cyclos.webservices.payments.PaymentParameters;
import nl.strohalm.cyclos.webservices.payments.PaymentStatus;
import nl.strohalm.cyclos.webservices.payments.RequestPaymentParameters;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException;
/**
* Helper class for web services payments<br>
* <b>WARN</b>: Be aware that this helper <b>doesn't</b> access the services through the security layer. They are all local services.
* @author luis
*/
public class PaymentHelper {
private ChannelHelper channelHelper;
private PaymentCustomFieldServiceLocal paymentCustomFieldServiceLocal;
private CurrencyHelper currencyHelper;
private TransferTypeServiceLocal transferTypeServiceLocal;
private ServiceClientServiceLocal serviceClientServiceLocal;
private MemberHelper memberHelper;
private MemberServiceLocal memberServiceLocal;
private AccountServiceLocal accountServiceLocal;
private QueryHelper queryHelper;
private CustomFieldHelper customFieldHelper;
private SettingsServiceLocal settingsServiceLocal;
/**
* Lists the possible transfer types for this payment
*/
public Collection<TransferType> listPossibleTypes(final DoPaymentDTO dto) {
final String channel = channelHelper.restricted();
if (StringUtils.isEmpty(channel)) {
return Collections.emptyList();
}
final Member member = WebServiceContext.getMember();
final ServiceClient client = WebServiceContext.getClient();
// First, we need a list of existing TTs for the payment
final TransferTypeQuery query = new TransferTypeQuery();
query.setResultType(ResultType.LIST);
query.setContext(TransactionContext.PAYMENT);
query.setChannel(channel);
query.setFromOwner(dto.getFrom());
query.setToOwner(dto.getTo());
query.setCurrency(dto.getCurrency());
final List<TransferType> transferTypes = transferTypeServiceLocal.search(query);
// Then, restrict according to the web service client permissions
boolean doPayment = true;
if (member != null) {
doPayment = member.equals(dto.getFrom());
}
Collection<TransferType> possibleTypes;
if (doPayment) {
possibleTypes = serviceClientServiceLocal.load(client.getId(), ServiceClient.Relationships.DO_PAYMENT_TYPES).getDoPaymentTypes();
} else {
possibleTypes = serviceClientServiceLocal.load(client.getId(), ServiceClient.Relationships.RECEIVE_PAYMENT_TYPES).getReceivePaymentTypes();
}
transferTypes.retainAll(possibleTypes);
return transferTypes;
}
/**
* Returns the from member, either by id or username
* @throws EntityNotFoundException When a member was expected (had either id or username) but not found
*/
public Member resolveFromMember(final AbstractPaymentParameters paymentParameters) throws EntityNotFoundException {
final String principalType = paymentParameters.getFromMemberPrincipalType();
final String principal = paymentParameters.getFromMember();
return resolveMember(principalType, principal, paymentParameters instanceof PaymentParameters && CollectionUtils.isNotEmpty(((PaymentParameters) paymentParameters).getFromMemberFieldsToReturn()));
}
/**
* Returns the to member, either by id or username
* @throws EntityNotFoundException When a member was expected (had either id or username) but not found
*/
public Member resolveToMember(final AbstractPaymentParameters paymentParameters) throws EntityNotFoundException {
final String principalType = paymentParameters.getToMemberPrincipalType();
final String principal = paymentParameters.getToMember();
return resolveMember(principalType, principal, paymentParameters instanceof PaymentParameters && CollectionUtils.isNotEmpty(((PaymentParameters) paymentParameters).getToMemberFieldsToReturn()));
}
public void setAccountServiceLocal(final AccountServiceLocal accountServiceLocal) {
this.accountServiceLocal = accountServiceLocal;
}
public void setChannelHelper(final ChannelHelper channelHelper) {
this.channelHelper = channelHelper;
}
public void setCurrencyHelper(final CurrencyHelper currencyHelper) {
this.currencyHelper = currencyHelper;
}
public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) {
this.customFieldHelper = customFieldHelper;
}
public void setMemberHelper(final MemberHelper memberHelper) {
this.memberHelper = memberHelper;
}
public void setMemberServiceLocal(final MemberServiceLocal memberServiceLocal) {
this.memberServiceLocal = memberServiceLocal;
}
public void setPaymentCustomFieldServiceLocal(final PaymentCustomFieldServiceLocal paymentCustomFieldService) {
paymentCustomFieldServiceLocal = paymentCustomFieldService;
}
public void setQueryHelper(final QueryHelper queryHelper) {
this.queryHelper = queryHelper;
}
public void setServiceClientServiceLocal(final ServiceClientServiceLocal serviceClientService) {
serviceClientServiceLocal = serviceClientService;
}
public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
settingsServiceLocal = settingsService;
}
public void setTransferTypeServiceLocal(final TransferTypeServiceLocal transferTypeService) {
transferTypeServiceLocal = transferTypeService;
}
/**
* Transform a payment parameters into a DoExternalPaymentDTO
*/
public DoPaymentDTO toExternalPaymentDTO(final AbstractPaymentParameters params, final AccountOwner from, final AccountOwner to) {
if (params == null) {
return null;
}
final DoPaymentDTO dto = new DoPaymentDTO();
dto.setAmount(params.getAmount());
dto.setCurrency(currencyHelper.resolve(params.getCurrency()));
dto.setDescription(params.getDescription());
dto.setFrom(from == null ? resolveFromMember(params) : from);
dto.setTo(to == null ? resolveToMember(params) : to);
// Do not accept empty trace numbers.
if (params.getTraceNumber() != null && params.getTraceNumber().trim().equals("")) {
throw new ValidationException();
}
dto.setTraceNumber(params.getTraceNumber());
dto.setTraceData(params.getTraceData());
// Handle specific types
if (params instanceof RequestPaymentParameters) {
// When requesting a payment, the payment itself will be from the destination channel
final RequestPaymentParameters request = (RequestPaymentParameters) params;
final String destinationChannel = request.getDestinationChannel();
dto.setChannel(destinationChannel);
if (destinationChannel == null) {
dto.setChannel(channelHelper.restricted());
}
} else if (params instanceof PaymentParameters) {
final PaymentParameters payment = (PaymentParameters) params;
dto.setChannel(channelHelper.restricted());
// Only positive id for transfer types
final Long transferTypeId = payment.getTransferTypeId();
if (transferTypeId != null && transferTypeId <= 0L) {
throw new ValidationException();
}
// Get the transfer type
TransferType transferType = null;
if (transferTypeId != null) {
transferType = transferTypeServiceLocal.load(transferTypeId);
}
if (transferType == null) {
// Try to find a default transfer type
final Collection<TransferType> possibleTypes = listPossibleTypes(dto);
if (possibleTypes.isEmpty()) {
throw new UnexpectedEntityException();
}
transferType = possibleTypes.iterator().next();
}
dto.setTransferType(transferType);
final List<FieldValueVO> fieldValueVOs = payment.getCustomValues();
List<PaymentCustomField> allowedFields = paymentCustomFieldServiceLocal.list(transferType, true);
final Collection<PaymentCustomFieldValue> customValues = customFieldHelper.toValueCollection(allowedFields, fieldValueVOs);
dto.setCustomValues(customValues);
// Set the default description as the transfer type description
if (StringUtils.isEmpty(payment.getDescription())) {
dto.setDescription(transferType.getDescription());
}
}
// Set the context, according to the current owners
if (dto.getFrom().equals(dto.getTo())) {
dto.setContext(TransactionContext.SELF_PAYMENT);
} else {
dto.setContext(TransactionContext.AUTOMATIC);
}
return dto;
}
/**
* Returns the payment status that corresponds to the given error
*/
public PaymentStatus toStatus(final Throwable error) {
if (error instanceof InvalidCredentialsException) {
return PaymentStatus.INVALID_CREDENTIALS;
} else if (error instanceof BlockedCredentialsException) {
return PaymentStatus.BLOCKED_CREDENTIALS;
} else if (error instanceof InvalidUserForChannelException) {
return PaymentStatus.INVALID_CHANNEL;
} else if (error instanceof NotEnoughCreditsException) {
return PaymentStatus.NOT_ENOUGH_CREDITS;
} else if (error instanceof UpperCreditLimitReachedException) {
return PaymentStatus.RECEIVER_UPPER_CREDIT_LIMIT_REACHED;
} else if (error instanceof MaxAmountPerDayExceededException) {
return PaymentStatus.MAX_DAILY_AMOUNT_EXCEEDED;
} else if (error instanceof IllegalArgumentException || error instanceof ValidationException || error instanceof UnexpectedEntityException || error instanceof EntityNotFoundException || error instanceof UserNotFoundException) {
return PaymentStatus.INVALID_PARAMETERS;
} else if (ExceptionUtils.indexOfThrowable(error, DataIntegrityViolationException.class) != -1) {
return PaymentStatus.INVALID_PARAMETERS;
} else {
return PaymentStatus.UNKNOWN_ERROR;
}
}
/**
* Return the payment status for the given transfer
*/
public PaymentStatus toStatus(final Transfer transfer) {
if (transfer.getProcessDate() == null) {
return PaymentStatus.PENDING_AUTHORIZATION;
} else {
return PaymentStatus.PROCESSED;
}
}
/**
* Transform a request payment parameters into a payment request ticket
*/
public PaymentRequestTicket toTicket(final RequestPaymentParameters params, final TransferType transferType) {
if (params == null) {
return null;
}
String description = params.getDescription();
if (StringUtils.isEmpty(description) && transferType != null) {
description = transferType.getDescription();
}
final PaymentRequestTicket ticket = new PaymentRequestTicket();
ticket.setTraceData(params.getTraceData());
ticket.setFromChannel(WebServiceContext.getChannel());
Channel toChannel = channelHelper.get(params.getDestinationChannel());
if (toChannel == null) {
toChannel = ticket.getFromChannel();
}
ticket.setToChannel(toChannel);
ticket.setAmount(params.getAmount());
ticket.setTransferType(transferType);
ticket.setDescription(description);
ticket.setCurrency(currencyHelper.resolve(params.getCurrency()));
return ticket;
}
/**
* Returns a list of TransactionFeeVO that contains information about the transaction fee and the amount that corresponds to the application of
* that fee.
* @param fees A map containing the transaction fee and the resulting amount of applying that fee.
*/
public List<TransactionFeeVO> toTransactionFeeVOs(final Map<TransactionFee, BigDecimal> fees) {
if (fees == null) {
return null;
}
List<TransactionFeeVO> result = new ArrayList<TransactionFeeVO>(fees.size());
final LocalSettings localSettings = settingsServiceLocal.getLocalSettings();
for (Map.Entry<TransactionFee, BigDecimal> entry : fees.entrySet()) {
TransactionFee key = entry.getKey();
BigDecimal value = entry.getValue();
String formattedAmount = localSettings.getUnitsConverter(key.getGeneratedTransferType().getCurrency().getPattern()).toString(value);
TransactionFeeVO transactionFeeVO = new TransactionFeeVO(key.getName(), value, formattedAmount);
result.add(transactionFeeVO);
}
return result;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public TransferQuery toTransferQuery(final AccountHistoryParams params) {
Member member = memberServiceLocal.loadByIdOrPrincipal(params.getMemberId(), null, params.getMemberPrincipal());
// Convert the params to a TransferQuery
TransferQuery query = new TransferQuery();
queryHelper.fill(params, query);
MemberAccount account = null;
if (EntityHelper.isValidId(params.getMemberAccountId())) {
account = accountServiceLocal.load(params.getMemberAccountId());
} else {
account = accountServiceLocal.getDefaultAccount();
}
query.setOwner(account.getOwner());
query.setType(account.getType());
query.setPeriod(Period.between(params.getBeginDate(), params.getEndDate()));
query.setMember(member);
query.setReverseOrder(true);
final List<PaymentCustomField> fieldsForSearch = paymentCustomFieldServiceLocal.listForSearch(account, false);
query.setCustomValues((Collection) customFieldHelper.toValueCollection(fieldsForSearch, params.getCustomValues()));
return query;
}
private Member resolveMember(final String principalType, final String principal, final boolean requiredFields) throws EntityNotFoundException {
if (requiredFields) {
return memberHelper.loadByPrincipal(principalType, principal, Member.Relationships.CUSTOM_VALUES);
} else {
return memberHelper.loadByPrincipal(principalType, principal);
}
}
}