/*
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.payment;
import com.sapienter.jbilling.common.SessionInternalError;
import com.sapienter.jbilling.server.invoice.InvoiceIdComparator;
import com.sapienter.jbilling.server.invoice.db.InvoiceDAS;
import com.sapienter.jbilling.server.invoice.db.InvoiceDTO;
import com.sapienter.jbilling.server.list.ResultList;
import com.sapienter.jbilling.server.notification.INotificationSessionBean;
import com.sapienter.jbilling.server.notification.MessageDTO;
import com.sapienter.jbilling.server.notification.NotificationBL;
import com.sapienter.jbilling.server.notification.NotificationNotFoundException;
import com.sapienter.jbilling.server.payment.db.PaymentAuthorizationDTO;
import com.sapienter.jbilling.server.payment.db.PaymentDAS;
import com.sapienter.jbilling.server.payment.db.PaymentDTO;
import com.sapienter.jbilling.server.payment.db.PaymentInfoChequeDAS;
import com.sapienter.jbilling.server.payment.db.PaymentInfoChequeDTO;
import com.sapienter.jbilling.server.payment.db.PaymentInvoiceMapDAS;
import com.sapienter.jbilling.server.payment.db.PaymentInvoiceMapDTO;
import com.sapienter.jbilling.server.payment.db.PaymentMethodDAS;
import com.sapienter.jbilling.server.payment.db.PaymentMethodDTO;
import com.sapienter.jbilling.server.payment.db.PaymentResultDAS;
import com.sapienter.jbilling.server.payment.db.PaymentResultDTO;
import com.sapienter.jbilling.server.payment.event.AbstractPaymentEvent;
import com.sapienter.jbilling.server.payment.event.PaymentDeletedEvent;
import com.sapienter.jbilling.server.pluggableTask.PaymentInfoTask;
import com.sapienter.jbilling.server.pluggableTask.PaymentTask;
import com.sapienter.jbilling.server.pluggableTask.TaskException;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskManager;
import com.sapienter.jbilling.server.system.event.EventManager;
import com.sapienter.jbilling.server.user.AchBL;
import com.sapienter.jbilling.server.user.CreditCardBL;
import com.sapienter.jbilling.server.user.db.CompanyDTO;
import com.sapienter.jbilling.server.user.db.CreditCardDAS;
import com.sapienter.jbilling.server.user.db.CreditCardDTO;
import com.sapienter.jbilling.server.user.db.UserDAS;
import com.sapienter.jbilling.server.user.partner.db.PartnerPayout;
import com.sapienter.jbilling.server.util.Constants;
import com.sapienter.jbilling.server.util.Context;
import com.sapienter.jbilling.server.util.audit.EventLogger;
import org.apache.log4j.Logger;
import org.springframework.dao.EmptyResultDataAccessException;
import javax.sql.rowset.CachedRowSet;
import javax.persistence.EntityNotFoundException;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class PaymentBL extends ResultList implements PaymentSQL {
private static final Logger LOG = Logger.getLogger(PaymentBL.class);
private PaymentDAS paymentDas = null;
private PaymentInfoChequeDAS chequeDas = null;
private CreditCardDAS ccDas = null;
private PaymentMethodDAS methodDas = null;
private PaymentInvoiceMapDAS mapDas = null;
private PaymentDTO payment = null;
private EventLogger eLogger = null;
public PaymentBL(Integer paymentId) {
init();
set(paymentId);
}
public PaymentBL() {
init();
}
public PaymentBL(PaymentDTO payment) {
init();
this.payment = payment;
}
public void set(PaymentDTO payment) {
this.payment = payment;
}
private void init() {
try {
eLogger = EventLogger.getInstance();
paymentDas = new PaymentDAS();
chequeDas = new PaymentInfoChequeDAS();
ccDas = new CreditCardDAS();
methodDas = new PaymentMethodDAS();
mapDas = new PaymentInvoiceMapDAS();
} catch (Exception e) {
throw new SessionInternalError(e);
}
}
public PaymentDTO getEntity() {
return payment;
}
public PaymentDAS getHome() {
return paymentDas;
}
public String getMethodDescription(PaymentMethodDTO method, Integer languageId) {
// load directly from the DB, otherwise proxies get in the way
return new PaymentMethodDAS().find(method.getId()).getDescription(languageId);
}
public void set(Integer id) {
payment = paymentDas.find(id);
}
public void create(PaymentDTOEx dto) {
// create the record
payment = paymentDas.create(dto.getAmount(), dto.getPaymentMethod(),
dto.getUserId(), dto.getAttempt(), dto.getPaymentResult(), dto.getCurrency());
payment.setPaymentDate(dto.getPaymentDate());
payment.setBalance(dto.getBalance());
// now verify if an info record should be created as well
if (dto.getCheque() != null) {
// create the db record
PaymentInfoChequeDTO cheque = chequeDas.create();
cheque.setBank(dto.getCheque().getBank());
cheque.setNumber(dto.getCheque().getNumber());
cheque.setDate(dto.getCheque().getDate());
// update the relationship dto-info
payment.setPaymentInfoCheque(cheque);
}
if (dto.getCreditCard() != null) {
dto.getCreditCard().getPayments().add(payment); // back reference to payment
CreditCardBL cc = new CreditCardBL();
cc.create(dto.getCreditCard());
payment.setCreditCard(cc.getEntity());
}
if (dto.getAch() != null) {
dto.getAch().getPayments().add(payment); // back reference to payment
AchBL achBl = new AchBL();
achBl.create(dto.getAch());
payment.setAch(achBl.getEntity());
}
// may be this is a refund
if (dto.getIsRefund() == 1) {
payment.setIsRefund(new Integer(1));
// now all refunds have balance = 0
payment.setBalance(BigDecimal.ZERO);
if (dto.getPayment() != null) {
// this refund is link to a payment
PaymentBL linkedPayment = new PaymentBL(dto.getPayment().getId());
payment.setPayment(linkedPayment.getEntity());
}
}
// preauth payments
if (dto.getIsPreauth() != null && dto.getIsPreauth().intValue() == 1) {
payment.setIsPreauth(1);
}
// the payment period length this payment was expected to last
if (dto.getPaymentPeriod() != null){
payment.setPaymentPeriod(dto.getPaymentPeriod());
}
// the notes related to this payment
if (dto.getPaymentNotes() != null){
payment.setPaymentNotes(dto.getPaymentNotes());
}
dto.setId(payment.getId());
dto.setCurrency(payment.getCurrency());
paymentDas.save(payment);
// add a log row for convenience
UserDAS user = new UserDAS();
eLogger.auditBySystem(user.find(dto.getUserId()).getCompany().getId(),
dto.getUserId(), Constants.TABLE_PAYMENT, dto.getId(),
EventLogger.MODULE_PAYMENT_MAINTENANCE,
EventLogger.ROW_CREATED, null, null, null);
}
void createMap(InvoiceDTO invoice, BigDecimal amount) {
BigDecimal realAmount;
if (new Integer(payment.getPaymentResult().getId()).equals(Constants.RESULT_FAIL) || new Integer(payment.getPaymentResult().getId()).equals(Constants.RESULT_UNAVAILABLE)) {
realAmount = BigDecimal.ZERO;
} else {
realAmount = amount;
}
mapDas.create(invoice, payment, realAmount);
}
/**
* Updates a payment record, including related cheque or credit card
* records. Only valid for entered payments not linked to an invoice.
*
* @param dto
* The DTO with all the information of the new payment record.
*/
public void update(Integer executorId, PaymentDTOEx dto)
throws SessionInternalError {
// the payment should've been already set when constructing this
// object
if (payment == null) {
throw new EmptyResultDataAccessException("Payment to update not set", 1);
}
// we better log this, so this change can be traced
eLogger.audit(executorId, payment.getBaseUser().getId(),
Constants.TABLE_PAYMENT, payment.getId(),
EventLogger.MODULE_PAYMENT_MAINTENANCE,
EventLogger.ROW_UPDATED, null, payment.getAmount().toString(),
null);
// start with the payment's own fields
payment.setUpdateDatetime(Calendar.getInstance().getTime());
payment.setAmount(dto.getAmount());
// since the payment can't be linked to an invoice, the balance
// has to be equal to the total of the payment
payment.setBalance(dto.getAmount());
payment.setPaymentDate(dto.getPaymentDate());
// now the records related to the method
if (dto.getCheque() != null) {
PaymentInfoChequeDTO cheque = payment.getPaymentInfoCheque();
cheque.setBank(dto.getCheque().getBank());
cheque.setNumber(dto.getCheque().getNumber());
cheque.setDate(dto.getCheque().getDate());
} else if (dto.getCreditCard() != null) {
CreditCardBL cc = new CreditCardBL(payment.getCreditCard());
cc.update(executorId, dto.getCreditCard(), null);
} else if (dto.getAch() != null) {
AchBL achBl = new AchBL(payment.getAch());
achBl.update(executorId, dto.getAch());
}
// the payment period length this payment was expected to last
if (dto.getPaymentPeriod() != null){
payment.setPaymentPeriod(dto.getPaymentPeriod());
}
// the notes related to this payment
if (dto.getPaymentNotes() != null){
payment.setPaymentNotes(dto.getPaymentNotes());
}
}
/**
* Goes through the payment pluggable tasks, and calls them with the payment
* information to get the payment processed. If a call fails because of the
* availability of the processor, it will try with the next task. Otherwise
* it will return the result of the process (approved or declined).
*
* @return the constant of the result allowing for the caller to attempt it
* again with different payment information (like another cc number)
*/
public Integer processPayment(Integer entityId, PaymentDTOEx info)
throws SessionInternalError {
Integer retValue = null;
try {
PluggableTaskManager taskManager = new PluggableTaskManager(
entityId, Constants.PLUGGABLE_TASK_PAYMENT);
PaymentTask task = (PaymentTask) taskManager.getNextClass();
if (task == null) {
// at least there has to be one task configurated !
LOG.warn("No payment pluggable" + "tasks configurated for entity " + entityId);
return null;
}
create(info);
boolean processorUnavailable = true;
while (task != null && processorUnavailable) {
// see if this user has pre-auths
PaymentAuthorizationBL authBL = new PaymentAuthorizationBL();
PaymentAuthorizationDTO auth = authBL.getPreAuthorization(info.getUserId());
if (auth != null) {
processorUnavailable = task.confirmPreAuth(auth, info);
if (!processorUnavailable) {
if (new Integer(info.getPaymentResult().getId()).equals(Constants.RESULT_FAIL)) {
processorUnavailable = task.process(info);
}
// in any case, don't use this preAuth again
authBL.markAsUsed(info);
}
} else {
// get this payment processed
processorUnavailable = task.process(info);
}
// allow the pluggable task to do something if the payment
// failed (like notification, suspension, etc ... )
if (!processorUnavailable && new Integer(info.getPaymentResult().getId()).equals(Constants.RESULT_FAIL)) {
task.failure(info.getUserId(), info.getAttempt());
}
// trigger an event
AbstractPaymentEvent event = AbstractPaymentEvent.forPaymentResult(entityId, info);
if (event != null) {
EventManager.process(event);
}
// get the next task
LOG.debug("Getting next task, processorUnavailable : " + processorUnavailable);
task = (PaymentTask) taskManager.getNextClass();
}
// if after all the tasks, the processor in unavailable,
// return that
if (processorUnavailable) {
retValue = Constants.RESULT_UNAVAILABLE;
} else {
retValue = info.getPaymentResult().getId();
}
// the balance of the payment depends on the result
if (retValue.equals(Constants.RESULT_OK) || retValue.equals(Constants.RESULT_ENTERED)) {
payment.setBalance(payment.getAmount());
} else {
payment.setBalance(BigDecimal.ZERO);
}
} catch (Exception e) {
LOG.fatal("Problems handling payment task.", e);
throw new SessionInternalError("Problems handling payment task.");
}
// add a notification to the user if the payment was good or bad
if (retValue.equals(Constants.RESULT_OK) || retValue.equals(Constants.RESULT_FAIL)) {
sendNotification(info, entityId);
}
// obscure credit cards used for one-time payments
if (payment.getCreditCard() != null && payment.getCreditCard().getBaseUsers().isEmpty()) {
payment.getCreditCard().obscureNumber();
}
return retValue;
}
public PaymentDTO getDTO() {
return new PaymentDTO(payment.getId(), payment.getAmount(), payment.getBalance(), payment.getCreateDatetime(), payment.getUpdateDatetime(), payment.getPaymentDate(), payment.getAttempt(), payment.getDeleted(),
payment.getPaymentMethod(), payment.getPaymentResult(), payment.getIsRefund(), payment.getIsPreauth(), payment.getCurrency(), payment.getBaseUser());
}
public PaymentDTOEx getDTOEx(Integer language) {
PaymentDTOEx dto = new PaymentDTOEx(getDTO());
dto.setUserId(payment.getBaseUser().getUserId());
// now add all the invoices that were paid by this payment
Iterator it = payment.getInvoicesMap().iterator();
while (it.hasNext()) {
PaymentInvoiceMapDTO map = (PaymentInvoiceMapDTO) it.next();
dto.getInvoiceIds().add(map.getInvoiceEntity().getId());
dto.addPaymentMap(getMapDTO(map.getId()));
}
// cheque info if applies
PaymentInfoChequeDTO chequeDto = null;
if (payment.getPaymentInfoCheque() != null) {
chequeDto = new PaymentInfoChequeDTO();
chequeDto.setBank(payment.getPaymentInfoCheque().getBank());
chequeDto.setDate(payment.getPaymentInfoCheque().getDate());
chequeDto.setId(payment.getPaymentInfoCheque().getId());
chequeDto.setNumber(payment.getPaymentInfoCheque().getNumber());
}
dto.setCheque(chequeDto);
// credit card info if applies
CreditCardDTO ccDto = null;
if (payment.getCreditCard() != null) {
ccDto = new CreditCardDTO();
ccDto.setNumber(payment.getCreditCard().getNumber());
ccDto.setCcExpiry(payment.getCreditCard().getCcExpiry());
ccDto.setName(payment.getCreditCard().getName());
ccDto.setCcType(payment.getCreditCard().getCcType());
ccDto.setGatewayKey(payment.getCreditCard().getGatewayKey());
}
dto.setCreditCard(ccDto);
// ach if applies
if (payment.getAch() != null) {
AchBL achBl = new AchBL(payment.getAch());
dto.setAch(achBl.getDTO());
} else {
dto.setAch(null);
}
// payment method (international)
PaymentMethodDTO method = payment.getPaymentMethod();
dto.setMethod(method.getDescription(language));
// refund fields if applicable
dto.setIsRefund(payment.getIsRefund());
if (payment.getPayment() != null && payment.getId() != payment.getPayment().getId()) {
PaymentBL linkedPayment = new PaymentBL(payment.getPayment().getId());
dto.setPayment(linkedPayment.getDTOEx(language));
}
// the first authorization if any
if (!payment.getPaymentAuthorizations().isEmpty()) {
PaymentAuthorizationBL authBL = new PaymentAuthorizationBL(
(PaymentAuthorizationDTO) payment.getPaymentAuthorizations().iterator().next());
dto.setAuthorization(authBL.getDTO());
}
// the result in string mode (international)
if (payment.getPaymentResult() != null) {
PaymentResultDTO result = payment.getPaymentResult();
dto.setResultStr(result.getDescription(language));
}
// to which payout this payment has been included
if (payment.getPartnerPayouts().size() > 0) {
dto.setPayoutId(((PartnerPayout) payment.getPartnerPayouts().toArray()[0]).getId());
}
// the payment period length this payment was expected to last
if (payment.getPaymentPeriod() != null){
dto.setPaymentPeriod(payment.getPaymentPeriod());
}
// the notes related to this payment
if (payment.getPaymentNotes() != null){
dto.setPaymentNotes(payment.getPaymentNotes());
}
return dto;
}
public static PaymentWS getWS(PaymentDTOEx dto) {
PaymentWS ws = new PaymentWS();
ws.setId(dto.getId());
ws.setAmount(dto.getAmount());
ws.setAttempt(dto.getAttempt());
ws.setBalance(dto.getBalance());
ws.setCreateDatetime(dto.getCreateDatetime());
ws.setDeleted(dto.getDeleted());
ws.setIsPreauth(dto.getIsPreauth());
ws.setIsRefund(dto.getIsRefund());
ws.setPaymentDate(dto.getPaymentDate());
ws.setUpdateDatetime(dto.getUpdateDatetime());
ws.setPaymentNotes(dto.getPaymentNotes());
ws.setPaymentPeriod(dto.getPaymentPeriod());
if (dto.getCurrency() != null)
ws.setCurrencyId(dto.getCurrency().getId());
if (dto.getPaymentMethod() != null)
ws.setMethodId(dto.getPaymentMethod().getId());
if (dto.getPaymentResult() != null)
ws.setResultId(dto.getPaymentResult().getId());
if (dto.getCreditCard() != null) {
com.sapienter.jbilling.server.entity.CreditCardDTO ccDTO = new com.sapienter.jbilling.server.entity.CreditCardDTO();
ccDTO.setDeleted(dto.getCreditCard().getDeleted());
ccDTO.setExpiry(dto.getCreditCard().getCcExpiry());
ccDTO.setId(dto.getCreditCard().getId());
ccDTO.setName(dto.getCreditCard().getName());
ccDTO.setNumber(dto.getCreditCard().getCcNumberPlain());
ccDTO.setSecurityCode(dto.getCreditCard().getSecurityCode());
ccDTO.setType(dto.getCreditCard().getCcType());
ws.setCreditCard(ccDTO);
} else {
ws.setCreditCard(null);
}
ws.setUserId(dto.getUserId());
if (dto.getCheque() != null) {
com.sapienter.jbilling.server.entity.PaymentInfoChequeDTO chqDTO = new com.sapienter.jbilling.server.entity.PaymentInfoChequeDTO();
chqDTO.setBank(dto.getCheque().getBank());
chqDTO.setDate(dto.getCheque().getDate());
chqDTO.setId(dto.getCheque().getId());
chqDTO.setNumber(dto.getCheque().getNumber());
ws.setCheque(chqDTO);
} else {
ws.setCheque(null);
}
ws.setMethod(dto.getMethod());
if (dto.getAch() != null) {
com.sapienter.jbilling.server.entity.AchDTO achDTO = new com.sapienter.jbilling.server.entity.AchDTO();
achDTO.setAbaRouting(dto.getAch().getAbaRouting());
achDTO.setAccountName(dto.getAch().getAccountName());
achDTO.setAccountType(dto.getAch().getAccountType());
achDTO.setBankAccount(dto.getAch().getBankAccount());
achDTO.setBankName(dto.getAch().getBankName());
achDTO.setGatewayKey(dto.getAch().getGatewayKey());
achDTO.setId(dto.getAch().getId());
ws.setAch(achDTO);
} else {
ws.setAch(null);
}
if (dto.getAuthorization() != null) {
com.sapienter.jbilling.server.entity.PaymentAuthorizationDTO authDTO = new com.sapienter.jbilling.server.entity.PaymentAuthorizationDTO();
authDTO.setAVS(dto.getAuthorization().getAvs());
authDTO.setApprovalCode(dto.getAuthorization().getApprovalCode());
authDTO.setCardCode(dto.getAuthorization().getCardCode());
authDTO.setCode1(dto.getAuthorization().getCode1());
authDTO.setCode2(dto.getAuthorization().getCode2());
authDTO.setCode3(dto.getAuthorization().getCode3());
authDTO.setCreateDate(dto.getAuthorization().getCreateDate());
authDTO.setId(dto.getAuthorization().getId());
authDTO.setMD5(dto.getAuthorization().getMD5());
authDTO.setProcessor(dto.getAuthorization().getProcessor());
authDTO.setResponseMessage(dto.getAuthorization().getResponseMessage());
authDTO.setTransactionId(dto.getAuthorization().getTransactionId());
ws.setAuthorization(authDTO);
} else {
ws.setAuthorization(null);
}
Integer invoiceIds[] = new Integer[dto.getInvoiceIds().size()];
for (int f = 0; f < dto.getInvoiceIds().size(); f++) {
invoiceIds[f] = (Integer) dto.getInvoiceIds().get(f);
}
ws.setInvoiceIds(invoiceIds);
if (dto.getPayment() != null) {
ws.setPaymentId(dto.getPayment().getId());
} else {
ws.setPaymentId(null);
}
return ws;
}
public CachedRowSet getList(Integer entityID, Integer languageId,
Integer userRole, Integer userId, boolean isRefund)
throws SQLException, Exception {
// the first variable specifies if this is a normal payment or
// a refund list
if (userRole.equals(Constants.TYPE_ROOT) || userRole.equals(Constants.TYPE_CLERK)) {
prepareStatement(PaymentSQL.rootClerkList);
cachedResults.setInt(1, isRefund ? 1 : 0);
cachedResults.setInt(2, entityID.intValue());
cachedResults.setInt(3, languageId.intValue());
} else if (userRole.equals(Constants.TYPE_PARTNER)) {
prepareStatement(PaymentSQL.partnerList);
cachedResults.setInt(1, isRefund ? 1 : 0);
cachedResults.setInt(2, entityID.intValue());
cachedResults.setInt(3, userId.intValue());
cachedResults.setInt(4, languageId.intValue());
} else if (userRole.equals(Constants.TYPE_CUSTOMER)) {
prepareStatement(PaymentSQL.customerList);
cachedResults.setInt(1, isRefund ? 1 : 0);
cachedResults.setInt(2, userId.intValue());
cachedResults.setInt(3, languageId.intValue());
} else {
throw new Exception("The payments list for the type " + userRole + " is not supported");
}
execute();
conn.close();
return cachedResults;
}
/**
* Does the actual work of deleteing the payment
*
* @throws SessionInternalError
*/
public void delete() throws SessionInternalError {
try {
LOG.debug("Deleting payment " + payment.getId());
Integer entityId = payment.getBaseUser().getEntity().getId();
EventManager.process(new PaymentDeletedEvent(entityId, payment));
payment.setUpdateDatetime(Calendar.getInstance().getTime());
payment.setDeleted(new Integer(1));
eLogger.auditBySystem(entityId, payment.getBaseUser().getId(),
Constants.TABLE_PAYMENT, payment.getId(),
EventLogger.MODULE_PAYMENT_MAINTENANCE,
EventLogger.ROW_DELETED, null, null, null);
} catch (Exception e) {
LOG.warn("Problem deleteing payment.", e);
throw new SessionInternalError("Problem deleteing payment.");
}
}
/*
* This is the list of payment that are refundable. It shows when entering a
* refund.
*/
public CachedRowSet getRefundableList(Integer languageId, Integer userId)
throws SQLException, Exception {
prepareStatement(PaymentSQL.refundableList);
cachedResults.setInt(1, 0); // is not a refund
cachedResults.setInt(2, userId.intValue());
cachedResults.setInt(3, languageId.intValue());
execute();
conn.close();
return cachedResults;
}
public boolean isMethodAccepted(Integer entityId, Integer paymentMethodId) {
boolean retValue = false;
PaymentMethodDTO method = methodDas.find(paymentMethodId);
for (Iterator it = method.getEntities().iterator(); it.hasNext();) {
if (((CompanyDTO) it.next()).getId() == entityId) {
retValue = true;
break;
}
}
return retValue;
}
public static PaymentDTOEx findPaymentInstrument(Integer entityId,
Integer userId) throws PluggableTaskException,
SessionInternalError, TaskException {
PluggableTaskManager taskManager = new PluggableTaskManager(entityId,
Constants.PLUGGABLE_TASK_PAYMENT_INFO);
PaymentInfoTask task = (PaymentInfoTask) taskManager.getNextClass();
if (task == null) {
// at least there has to be one task configurated !
Logger.getLogger(PaymentBL.class).fatal(
"No payment info pluggable" + "tasks configurated for entity " + entityId);
throw new SessionInternalError("No payment info pluggable" + "tasks configurated for entity " + entityId);
}
// get this payment information. Now we only expect one pl.tsk
// to get the info, I don't see how more could help
return task.getPaymentInfo(userId);
}
public static boolean validate(PaymentWS dto) {
boolean retValue = true;
if (dto.getAmount() == null || dto.getMethodId() == null || dto.getIsRefund() == 0 || dto.getResultId() == null || dto.getUserId() == null || (dto.getCheque() == null && dto.getCreditCard() == null)) {
retValue = false;
} else if (dto.getCreditCard() != null) {
PaymentDTOEx ex = new PaymentDTOEx(dto);
retValue = CreditCardBL.validate(ex.getCreditCard());
} else if (dto.getCheque() != null) {
PaymentDTOEx ex = new PaymentDTOEx(dto);
retValue = validate(ex.getCheque());
}
return retValue;
}
public static boolean validate(PaymentInfoChequeDTO dto) {
boolean retValue = true;
if (dto.getDate() == null || dto.getNumber() == null) {
retValue = false;
}
return retValue;
}
public Integer getLatest(Integer userId) throws SessionInternalError {
Integer retValue = null;
try {
prepareStatement(PaymentSQL.getLatest);
cachedResults.setInt(1, userId.intValue());
execute();
if (cachedResults.next()) {
int value = cachedResults.getInt(1);
if (!cachedResults.wasNull()) {
retValue = new Integer(value);
}
}
cachedResults.close();
conn.close();
} catch (Exception e) {
throw new SessionInternalError(e);
}
return retValue;
}
public Integer[] getManyWS(Integer userId, Integer number,
Integer languageId) {
List<Integer> result = new PaymentDAS().findIdsByUserLatestFirst(
userId, number);
return result.toArray(new Integer[result.size()]);
}
private List<PaymentDTO> getPaymentsWithBalance(Integer userId) {
// this will usually return 0 or 1 records, rearly a few more
List<PaymentDTO> paymentsList = null;
Collection payments = paymentDas.findWithBalance(userId);
if (payments != null) {
paymentsList = new ArrayList<PaymentDTO>(payments); // needed for the
// sort
Collections.sort(paymentsList, new PaymentEntityComparator());
Collections.reverse(paymentsList);
} else {
paymentsList = new ArrayList<PaymentDTO>(); // empty
}
return paymentsList;
}
/**
* Given an invoice, the system will look for any payment with a balance
* and get the invoice paid with this payment.
*/
public void automaticPaymentApplication(InvoiceDTO invoice)
throws SQLException {
List payments = getPaymentsWithBalance(invoice.getBaseUser().getUserId());
for (int f = 0; f < payments.size() && invoice.getBalance().compareTo(BigDecimal.ZERO) > 0; f++) {
payment = (PaymentDTO) payments.get(f);
if (new Integer(payment.getPaymentResult().getId()).equals(Constants.RESULT_FAIL) || new Integer(payment.getPaymentResult().getId()).equals(Constants.RESULT_UNAVAILABLE)) {
continue;
}
applyPaymentToInvoice(invoice);
}
}
/**
* Give an payment (already set in this object), it will look for any
* invoices with a balance and get them paid, starting wiht the oldest.
*/
public void automaticPaymentApplication() throws SQLException {
if (BigDecimal.ZERO.compareTo(payment.getBalance()) >= 0) {
return; // negative payment, skip
}
Collection<InvoiceDTO> invoiceCollection = new InvoiceDAS().findWithBalanceByUser(payment.getBaseUser());
// sort from oldest to newest
List<InvoiceDTO> invoices = new ArrayList<InvoiceDTO>(invoiceCollection);
Collections.sort(invoices, new InvoiceIdComparator());
for (InvoiceDTO invoice : invoices) {
// negative balances don't need paying
if (BigDecimal.ZERO.compareTo(invoice.getBalance()) > 0) {
continue;
}
applyPaymentToInvoice(invoice);
if (BigDecimal.ZERO.compareTo(payment.getBalance()) >= 0) {
break; // no payment balance remaining
}
}
}
private void applyPaymentToInvoice(InvoiceDTO invoice) throws SQLException {
// this is not actually getting de Ex, so it is faster
PaymentDTOEx dto = new PaymentDTOEx(getDTO());
// not pretty, but the methods are there
IPaymentSessionBean psb = (IPaymentSessionBean) Context.getBean(
Context.Name.PAYMENT_SESSION);
// make the link between the payment and the invoice
BigDecimal paidAmount = psb.applyPayment(dto, invoice, true);
createMap(invoice, paidAmount);
// notify the customer
dto.setUserId(invoice.getBaseUser().getUserId()); // needed for the
// notification
// the notification only understands ok or not, if the payment is
// entered
// it has to show as ok
dto.setPaymentResult(new PaymentResultDAS().find(Constants.RESULT_OK));
sendNotification(dto, payment.getBaseUser().getEntity().getId());
}
/**
* sends an notification with a payment
*/
public void sendNotification(PaymentDTOEx info, Integer entityId) {
try {
NotificationBL notif = new NotificationBL();
MessageDTO message = notif.getPaymentMessage(entityId, info,
new Integer(info.getPaymentResult().getId()).equals(Constants.RESULT_OK));
INotificationSessionBean notificationSess =
(INotificationSessionBean) Context.getBean(
Context.Name.NOTIFICATION_SESSION);
notificationSess.notify(info.getUserId(), message);
} catch (NotificationNotFoundException e1) {
// won't send anyting because the entity didn't specify the
// notification
LOG.warn("Can not notify a customer about a payment " +
"beacuse the entity lacks the notification. " +
"entity = " + entityId);
}
}
/*
* The payment doesn't have to be set. It adjusts the balances of both the
* payment and the invoice and deletes the map row.
*/
public void removeInvoiceLink(Integer mapId) {
try {
// find the map
PaymentInvoiceMapDTO map = mapDas.find(mapId);
// start returning the money to the payment's balance
BigDecimal amount = map.getAmount();
payment = map.getPayment();
amount = amount.add(payment.getBalance());
payment.setBalance(amount);
// the balace of the invoice also increases
InvoiceDTO invoice = map.getInvoiceEntity();
amount = map.getAmount().add(invoice.getBalance());
invoice.setBalance(amount);
// this invoice probably has to be paid now
if (Constants.BIGDECIMAL_ONE_CENT.compareTo(invoice.getBalance()) <= 0) {
invoice.setToProcess(1);
}
// log that this was deleted, otherwise there will be no trace
eLogger.info(invoice.getBaseUser().getEntity().getId(),
payment.getBaseUser().getId(), mapId,
EventLogger.MODULE_PAYMENT_MAINTENANCE,
EventLogger.ROW_DELETED,
Constants.TABLE_PAYMENT_INVOICE_MAP);
// get rid of the map all together
mapDas.delete(map);
} catch (EntityNotFoundException enfe) {
LOG.error("Exception removing payment-invoice link: EntityNotFoundException", enfe);
} catch (Exception e) {
LOG.error("Exception removing payment-invoice link", e);
throw new SessionInternalError(e);
}
}
/**
* This method removes the link between this payment and the
* <i>invoiceId</i> of the Invoice
* @param invoiceId Invoice Id to be unlinked from this payment
*/
public boolean unLinkFromInvoice(Integer invoiceId) {
InvoiceDTO invoice= new InvoiceDAS().find(invoiceId);
Iterator<PaymentInvoiceMapDTO> it = invoice.getPaymentMap().iterator();
boolean bSucceeded= false;
while (it.hasNext()) {
PaymentInvoiceMapDTO map = it.next();
if (this.payment.getId() == map.getPayment().getId()) {
this.removeInvoiceLink(map.getId());
invoice.getPaymentMap().remove(map);
bSucceeded=true;
break;
}
}
return bSucceeded;
}
public PaymentInvoiceMapDTOEx getMapDTO(Integer mapId) {
// find the map
PaymentInvoiceMapDTO map = mapDas.find(mapId);
PaymentInvoiceMapDTOEx dto = new PaymentInvoiceMapDTOEx(map.getId(), map.getAmount(), map.getCreateDatetime());
dto.setPaymentId(map.getPayment().getId());
dto.setInvoiceId(map.getInvoiceEntity().getId());
dto.setCurrencyId(map.getPayment().getCurrency().getId());
return dto;
}
}