/* 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.user.tasks; import com.sapienter.jbilling.common.Constants; import com.sapienter.jbilling.server.payment.IPaymentSessionBean; import com.sapienter.jbilling.server.payment.PaymentBL; import com.sapienter.jbilling.server.payment.PaymentDTOEx; import com.sapienter.jbilling.server.payment.PaymentSessionBean; import com.sapienter.jbilling.server.pluggableTask.PluggableTask; import com.sapienter.jbilling.server.pluggableTask.TaskException; import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException; import com.sapienter.jbilling.server.system.event.Event; import com.sapienter.jbilling.server.system.event.task.IInternalEventsTask; import com.sapienter.jbilling.server.user.UserBL; import com.sapienter.jbilling.server.user.db.CustomerDTO; import com.sapienter.jbilling.server.user.db.UserDTO; import com.sapienter.jbilling.server.user.event.DynamicBalanceChangeEvent; import com.sapienter.jbilling.server.util.PreferenceBL; import org.apache.log4j.Logger; import org.springframework.dao.EmptyResultDataAccessException; import java.math.BigDecimal; import java.util.Date; /** * Automatic payment task designed to "top up" a customers pre-paid balance with a user * configured amount whenever the balance drops below a company wide threshold (configured * as a preference). * * This task subscribes to the {@link DynamicBalanceChangeEvent} which is fired whenever * the customers balance changes. * * @see com.sapienter.jbilling.server.user.balance.DynamicBalanceManagerTask * * @author Brian Cowdery * @since 10-14-2009 */ public class AutoRechargeTask extends PluggableTask implements IInternalEventsTask { private static final Logger LOG = Logger.getLogger(AutoRechargeTask.class); @SuppressWarnings("unchecked") private static final Class<Event>[] events = new Class[]{ DynamicBalanceChangeEvent.class }; public Class<Event>[] getSubscribedEvents() { return events; } public void process(Event event) throws PluggableTaskException { if (!(event instanceof DynamicBalanceChangeEvent)) { throw new PluggableTaskException("Cannot process event " + event); } DynamicBalanceChangeEvent balanceEvent = (DynamicBalanceChangeEvent) event; UserDTO user = new UserBL(balanceEvent.getUserId()).getDto(); CustomerDTO customer = user.getCustomer(); LOG.debug("Processing " + event); if (!isEventProcessable(balanceEvent.getNewBalance(), user, customer)) { LOG.debug("Conditions not met, no recharge"); return; } PaymentDTOEx payment = null; try { payment = PaymentBL.findPaymentInstrument(event.getEntityId(), user.getId()); } catch (TaskException e) { throw new PluggableTaskException(e); } if (payment != null) { payment.setIsRefund(0); payment.setAttempt(1); payment.setAmount(customer.getAutoRecharge()); payment.setCurrency(user.getCurrency()); payment.setUserId(user.getId()); payment.setPaymentDate(new Date()); LOG.debug("Making automatic payment of $" + payment.getAmount() + " for user " + payment.getUserId()); // can't use the managed bean, a new transaction will cause the CustomerDTO to get an // optimistic lock: this transaction and the new payment one both changing the same customer.dynamic_balance IPaymentSessionBean paymentSession = new PaymentSessionBean(); Integer result = paymentSession.processAndUpdateInvoice(payment, null, balanceEvent.getEntityId()); LOG.debug("Payment created with result: " + result); } else { LOG.debug("No payment instrument, no recharge"); } } /** * Returns true if the auto-recharge criteria has been met and this event can be processed. * * @param newBalance new dynamic balance of the user * @param user user to validate * @param customer customer to validate * @return true if event can be processed, false if not. * @throws PluggableTaskException */ private boolean isEventProcessable(BigDecimal newBalance, UserDTO user, CustomerDTO customer) { if (customer == null || customer.getAutoRecharge().compareTo(BigDecimal.ZERO) <= 0) { LOG.debug("Not a customer, or auto recharge value <= 0"); return false; } BigDecimal threshold = getAutoRechargeThreshold(user.getEntity().getId()); if (threshold != null && threshold.compareTo(newBalance) > 0) { if (!Constants.BALANCE_PRE_PAID.equals(customer.getBalanceType())) { LOG.debug("User " + user.getId() + " does not hold a pre-paid balance, cannot make automatic payment!"); return false; } } else { LOG.debug("Company does not have a recharge preference, or this customer balance not reached the threshold"); return false; } return true; } /** * Returns the set auto-recharge threshold for the given entity id, or null * if the company does not have a configured threshold. * * @param entityId entity id * @return auto-recharge threshold or null if not set */ private BigDecimal getAutoRechargeThreshold(Integer entityId) { PreferenceBL preference = new PreferenceBL(); try { preference.set(entityId, Constants.PREFERENCE_AUTO_RECHARGE_THRESHOLD); } catch (EmptyResultDataAccessException e) { return null; // no threshold set } return new BigDecimal(preference.getFloat()); } }