/*
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.controls.payments;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.controls.ActionContext;
import nl.strohalm.cyclos.controls.BaseFormAction;
import nl.strohalm.cyclos.entities.accounts.AccountOwner;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.transactions.Payment;
import nl.strohalm.cyclos.entities.accounts.transactions.ScheduledPayment;
import nl.strohalm.cyclos.entities.accounts.transactions.Transfer;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.customization.fields.CustomField;
import nl.strohalm.cyclos.entities.customization.fields.CustomFieldPossibleValue;
import nl.strohalm.cyclos.entities.customization.fields.CustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomField;
import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomFieldValue;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.services.customization.PaymentCustomFieldService;
import nl.strohalm.cyclos.services.transactions.DoPaymentDTO;
import nl.strohalm.cyclos.services.transactions.PaymentService;
import nl.strohalm.cyclos.services.transactions.ScheduledPaymentDTO;
import nl.strohalm.cyclos.services.transactions.exceptions.AuthorizedPaymentInPastException;
import nl.strohalm.cyclos.services.transactions.exceptions.CreditsException;
import nl.strohalm.cyclos.services.transfertypes.TransactionFeePreviewDTO;
import nl.strohalm.cyclos.services.transfertypes.TransactionFeeService;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeService;
import nl.strohalm.cyclos.utils.ActionHelper;
import nl.strohalm.cyclos.utils.CustomFieldHelper;
import nl.strohalm.cyclos.utils.CustomFieldHelper.Entry;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.validation.RequiredError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForward;
/**
* Action used to display a message and optionally request the transaction password in order to make a payment
* @author luis
*/
public class ConfirmPaymentAction extends BaseFormAction {
private PaymentService paymentService;
private TransferTypeService transferTypeService;
private TransactionFeeService transactionFeeService;
private PaymentCustomFieldService paymentCustomFieldService;
private CustomFieldHelper customFieldHelper;
@Inject
public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) {
this.customFieldHelper = customFieldHelper;
}
@Inject
public void setPaymentCustomFieldService(final PaymentCustomFieldService paymentCustomFieldService) {
this.paymentCustomFieldService = paymentCustomFieldService;
}
@Inject
public void setPaymentService(final PaymentService paymentService) {
this.paymentService = paymentService;
}
@Inject
public void setTransactionFeeService(final TransactionFeeService transactionFeeService) {
this.transactionFeeService = transactionFeeService;
}
@Inject
public void setTransferTypeService(final TransferTypeService transferTypeService) {
this.transferTypeService = transferTypeService;
}
@Override
protected ActionForward handleSubmit(final ActionContext context) throws Exception {
final ConfirmPaymentForm form = context.getForm();
final DoPaymentDTO paymentDTO = validatePayment(context);
// Validate the transaction password if needed
if (shouldValidateTransactionPassword(context, paymentDTO)) {
context.checkTransactionPassword(form.getTransactionPassword());
}
// Perform the actual payment
Payment payment;
try {
payment = paymentService.doPayment(paymentDTO);
context.getSession().removeAttribute("payment");
} catch (final CreditsException e) {
return context.sendError(actionHelper.resolveErrorKey(e), actionHelper.resolveParameters(e));
} catch (final UnexpectedEntityException e) {
return context.sendError("payment.error.invalidTransferType");
} catch (final AuthorizedPaymentInPastException e) {
return context.sendError("payment.error.authorizedInPast");
}
// Redirect to the next action
final Map<String, Object> params = new HashMap<String, Object>();
ActionForward forward;
if (payment instanceof Transfer) {
params.put("transferId", payment.getId());
forward = context.getSuccessForward();
} else if (payment instanceof ScheduledPayment) {
params.put("paymentId", payment.getId());
forward = context.findForward("scheduledPayment");
} else {
throw new IllegalStateException("Unknown payment type: " + payment);
}
params.put("selectMember", form.getSelectMember());
params.put("from", form.getFrom());
return ActionHelper.redirectWithParams(context.getRequest(), forward, params);
}
@Override
protected void prepareForm(final ActionContext context) throws Exception {
final DoPaymentDTO payment = validatePayment(context);
// Clear the from when the same as logged owner
if (payment.getFrom() != null && context.getAccountOwner().equals(payment.getFrom())) {
payment.setFrom(null);
}
// Check for transaction password
final HttpServletRequest request = context.getRequest();
final boolean requestTransactionPassword = shouldValidateTransactionPassword(context, payment);
if (requestTransactionPassword) {
context.validateTransactionPassword();
}
final boolean wouldRequireAuthorization = paymentService.wouldRequireAuthorization(payment);
request.setAttribute("requestTransactionPassword", requestTransactionPassword);
request.setAttribute("wouldRequireAuthorization", wouldRequireAuthorization);
if (wouldRequireAuthorization && payment.getDate() != null) {
throw new ValidationException("payment.error.authorizedInPast");
}
// Fetch related data
AccountOwner from = payment.getFrom();
AccountOwner to = payment.getTo();
final TransferType transferType = transferTypeService.load(payment.getTransferType().getId(), RelationshipHelper.nested(TransferType.Relationships.FROM, AccountType.Relationships.CURRENCY), TransferType.Relationships.TO);
final BigDecimal amount = payment.getAmount();
if (from instanceof Member) {
from = (Member) elementService.load(((Member) from).getId());
request.setAttribute("fromMember", from);
payment.setFrom(from);
}
if (to instanceof Member) {
to = (Member) elementService.load(((Member) to).getId());
request.setAttribute("toMember", to);
payment.setTo(to);
}
// request.setAttribute("relatedMember", from != null ? from.g : to);
payment.setTransferType(transferType);
request.setAttribute("unitsPattern", transferType.getFrom().getCurrency().getPattern());
// Store the transaction fees
final TransactionFeePreviewDTO preview = transactionFeeService.preview(from, to, transferType, amount);
request.setAttribute("finalAmount", preview.getFinalAmount());
request.setAttribute("fees", preview.getFees());
// Show the total amount when the original amount has changed (there where fees which deducted from it)
if (!preview.getAmount().equals(preview.getFinalAmount())) {
request.setAttribute("totalAmount", preview.getAmount());
}
// Calculate the transaction fees for every scheduled payment
final List<ScheduledPaymentDTO> payments = payment.getPayments();
final boolean isScheduled = CollectionUtils.isNotEmpty(payments);
if (isScheduled) {
for (final ScheduledPaymentDTO current : payments) {
final TransactionFeePreviewDTO currentPreview = transactionFeeService.preview(from, to, transferType, current.getAmount());
current.setFinalAmount(currentPreview.getFinalAmount());
}
}
request.setAttribute("isScheduled", isScheduled);
// Return the custom field values
final Collection<PaymentCustomFieldValue> customValues = payment.getCustomValues();
if (customValues != null) {
final List<PaymentCustomField> customFields = paymentCustomFieldService.list(transferType, false);
final Collection<Entry> entries = customFieldHelper.buildEntries(customFields, customValues);
// Load the value for enumerated values, since this collection was built from direct databinding with ids only
for (final Entry entry : entries) {
final CustomField field = entry.getField();
final CustomFieldValue fieldValue = entry.getValue();
if (field.getType() == CustomField.Type.ENUMERATED) {
Long possibleValueId;
final CustomFieldPossibleValue possibleValue = fieldValue.getPossibleValue();
if (possibleValue != null) {
possibleValueId = possibleValue.getId();
} else {
possibleValueId = CoercionHelper.coerce(Long.class, fieldValue.getValue());
}
if (possibleValueId != null) {
fieldValue.setPossibleValue(paymentCustomFieldService.loadPossibleValue(possibleValueId));
}
} else if (field.getType() == CustomField.Type.MEMBER) {
final Long memberId = CoercionHelper.coerce(Long.class, fieldValue.getValue());
if (memberId != null) {
final Element element = elementService.load(memberId);
if (element instanceof Member) {
fieldValue.setMemberValue((Member) element);
}
}
}
}
request.setAttribute("customFields", entries);
}
}
@Override
protected void validateForm(final ActionContext context) {
if (shouldValidateTransactionPassword(context, validatePayment(context))) {
final ConfirmPaymentForm form = context.getForm();
if (StringUtils.isEmpty(form.getTransactionPassword())) {
throw new ValidationException("_transactionPassword", "login.transactionPassword", new RequiredError());
}
}
}
private boolean shouldValidateTransactionPassword(final ActionContext context, final DoPaymentDTO payment) {
if (payment.getFrom() == null) {
// When a logged member performing payments from himself
final TransferType transferType = transferTypeService.load(payment.getTransferType().getId(), TransferType.Relationships.FROM);
return context.isTransactionPasswordEnabled(transferType.getFrom());
} else {
return context.isTransactionPasswordEnabled();
}
}
private DoPaymentDTO validatePayment(final ActionContext context) {
final DoPaymentDTO payment = (DoPaymentDTO) context.getSession().getAttribute("payment");
if (payment == null) {
throw new ValidationException();
}
return payment;
}
}