/* 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; import java.math.BigDecimal; import java.sql.SQLException; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.Set; import org.apache.log4j.Logger; import com.sapienter.jbilling.common.SessionInternalError; import com.sapienter.jbilling.common.Util; 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.PaymentAuthorizationDTOEx; import com.sapienter.jbilling.server.payment.PaymentBL; import com.sapienter.jbilling.server.payment.PaymentDTOEx; import com.sapienter.jbilling.server.payment.db.PaymentDTO; import com.sapienter.jbilling.server.payment.db.PaymentMethodDAS; import com.sapienter.jbilling.server.payment.db.PaymentResultDAS; import com.sapienter.jbilling.server.payment.event.AbstractPaymentEvent; import com.sapienter.jbilling.server.pluggableTask.PaymentTask; 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.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.db.UserDTO; import com.sapienter.jbilling.server.user.event.NewCreditCardEvent; import com.sapienter.jbilling.server.util.Constants; import com.sapienter.jbilling.server.util.Context; import com.sapienter.jbilling.server.util.audit.EventLogger; import com.sapienter.jbilling.server.util.db.CurrencyDAS; public class CreditCardBL extends ResultList implements CreditCardSQL { private CreditCardDAS creditCardDas = null; private CreditCardDTO creditCard = null; private static final Logger LOG = Logger.getLogger(CreditCardBL.class); private EventLogger eLogger = null; public CreditCardBL(Integer creditCardId) { init(); set(creditCardId); } public CreditCardBL() { init(); } public CreditCardBL(CreditCardDTO row) { init(); creditCard = row; } private void init() { eLogger = EventLogger.getInstance(); creditCardDas = new CreditCardDAS(); } public CreditCardDTO getEntity() { return creditCard; } public void set(Integer id) { creditCard = creditCardDas.find(id); } public void set(CreditCardDTO pEntity) { creditCard = pEntity; } /** * Creates a new persistent CreditCardDTO and emits a NewCreditCardEvent. * * @param dto credit card to persist * @return id of persisted credit card */ public Integer create(CreditCardDTO dto) { dto.setId(0); dto.setVersionNum(null); dto.setDeleted(0); // Only save un-obscured credit cards. If a card is obscured, we assume that it is an // existing card stored against an external payment gateway - fetch from the db instead if (!dto.useGatewayKey() || !dto.isNumberObsucred()) { creditCard = creditCardDas.save(dto); UserDTO user = getUser(creditCard); EventManager.process(new NewCreditCardEvent(creditCard, (user == null ? null : user.getEntity().getId()))); LOG.debug("Saved new credit card " + creditCard.getId()); } else { UserDTO user = getUser(dto); creditCard = new UserBL(user.getId()).getCreditCard(); LOG.debug("Credit card obscured, using the stored credit card."); } return (creditCard == null ? null : creditCard.getId()); } /** * Get the associated user for this credit card. * * @return associated user, null if not found */ public UserDTO getUser() { return getUser(creditCard); } /** * Get the associated user for the given credit card. If the credit card is * a user credit card, the base user will be returned. If the credit card is being * used for a one-time payment, the user of the payment will be returned. * * @param dto credit card * @return associated user, null if not found */ public UserDTO getUser(CreditCardDTO dto) { if (dto != null) { if (!dto.getBaseUsers().isEmpty()) { // credit card saved for a user return dto.getBaseUsers().iterator().next(); } else if (!dto.getPayments().isEmpty()) { // credit card saved for a payment (cc not linked to a user) PaymentDTO payment = dto.getPayments().iterator().next(); return payment.getBaseUser(); } } return null; } public void update(Integer executorId, CreditCardDTO dto, Integer userId) throws SessionInternalError { if (executorId != null) { eLogger.audit(executorId, userId, Constants.TABLE_CREDIT_CARD, creditCard.getId(), EventLogger.MODULE_CREDIT_CARD_MAINTENANCE, EventLogger.ROW_UPDATED, null, null, creditCard.getCcExpiry()); } creditCard.setCcExpiry(dto.getCcExpiry()); creditCard.setName(dto.getName()); // the number can be null, because calls from the API would do this // to leave the number unchanged. Ignore masked numbers and leave number as-is. if (dto.getNumber() != null && !dto.getNumber().contains("*")) { creditCard.setNumber(dto.getNumber()); } creditCard.setDeleted(new Integer(0)); // remove any pre-authorization. Otherwise the next payment won't be // done with this new credit card if (userId != null) { PaymentBL paymentBl = new PaymentBL(); for (PaymentDTO auth : (Collection<PaymentDTO>) paymentBl.getHome().findPreauth(userId)) { LOG.debug("New credit card for user with pre-auths." + dto); paymentBl.set(auth); paymentBl.delete(); } } UserDTO userD = new UserDAS().find(userId); dto.getBaseUsers().add(userD); creditCard.setBaseUsers(dto.getBaseUsers()); userD.getCreditCards().add(creditCard); NewCreditCardEvent event = new NewCreditCardEvent(creditCard, userD.getCompany().getId()); EventManager.process(event); new UserDAS().save(userD); new CreditCardDAS().save(creditCard); } public void delete(Integer executorId) { // now delete this creditCard record eLogger.audit(executorId, null, Constants.TABLE_CREDIT_CARD, creditCard.getId(), EventLogger.MODULE_CREDIT_CARD_MAINTENANCE, EventLogger.ROW_DELETED, null, null, null); creditCard.setDeleted(new Integer(1)); Iterator<UserDTO> itera = creditCard.getBaseUsers().iterator(); while (itera.hasNext()) { UserDTO uus = itera.next(); uus.getCreditCards().remove(creditCard); itera.remove(); itera = creditCard.getBaseUsers().iterator(); new UserDAS().save(uus); new CreditCardDAS().save(creditCard); } } public void notifyExipration(Date today) throws SQLException, SessionInternalError { LOG.debug("Sending credit card expiration notifications. Today " + today); prepareStatement(CreditCardSQL.expiring); cachedResults.setDate(1, new java.sql.Date(today.getTime())); execute(); while (cachedResults.next()) { Integer userId = new Integer(cachedResults.getInt(1)); Integer ccId = new Integer(cachedResults.getInt(2)); set(ccId); NotificationBL notif = new NotificationBL(); UserBL user = new UserBL(userId); try { MessageDTO message = notif.getCreditCardMessage(user.getEntity(). getEntity().getId(), user.getEntity().getLanguageIdField(), userId, getDTO()); INotificationSessionBean notificationSess = (INotificationSessionBean) Context.getBean( Context.Name.NOTIFICATION_SESSION); notificationSess.notify(userId, message); } catch (NotificationNotFoundException e) { LOG.warn("credit card message not set to user " + userId + " because the entity lacks notification text"); } } conn.close(); } /** * Returns true if it makes sense to send this cc to the processor. * Otherwise false (like when the card is now expired). */ public boolean validate() { boolean retValue = true; if (creditCard.getCcExpiry().before(Calendar.getInstance().getTime())) { retValue = false; } else { if (Util.getPaymentMethod(creditCard.getNumber()) == null) { retValue = false; } } return retValue; } static public boolean validate(CreditCardDTO dto) { boolean retValue = true; if (dto.getCcExpiry() == null || dto.getName() == null || dto.getNumber() == null) { retValue = false; Logger.getLogger(CreditCardBL.class).debug("invalid " + dto); } return retValue; } public CreditCardDTO getDTO() { CreditCardDTO dto = new CreditCardDTO(); dto.setId(creditCard.getId()); dto.setDeleted(creditCard.getDeleted()); dto.setCcExpiry(creditCard.getCcExpiry()); dto.setName(creditCard.getName()); dto.setNumber(creditCard.getNumber()); dto.setCcType(creditCard.getCcType()); dto.setSecurityCode(creditCard.getSecurityCode()); dto.setGatewayKey(creditCard.getGatewayKey()); return dto; } public Integer getPaymentMethod() { return Util.getPaymentMethod(creditCard.getNumber()); } /** * removes spaces and '-' from the number. * @param number * @return */ public static String cleanUpNumber(String number) { return number.replaceAll("[-\\ ]", "").trim(); } /** * Only used from the API, thus the usage of PaymentAuthorizationDTOEx * @param entityId * @param userId * @param cc * @param amount * @param currencyId * @return * @throws com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException */ public PaymentAuthorizationDTOEx validatePreAuthorization(Integer entityId, Integer userId, CreditCardDTO cc, BigDecimal amount, Integer currencyId) throws PluggableTaskException { // create a new payment record PaymentDTOEx paymentDto = new PaymentDTOEx(); paymentDto.setAmount(amount); paymentDto.setCurrency(new CurrencyDAS().find(currencyId)); paymentDto.setCreditCard(cc); paymentDto.setUserId(userId); paymentDto.setIsPreauth(1); // filler fields, required paymentDto.setIsRefund(0); paymentDto.setPaymentMethod(new PaymentMethodDAS().find(Util.getPaymentMethod(cc.getNumber()))); paymentDto.setAttempt(1); paymentDto.setPaymentResult(new PaymentResultDAS().find(Constants.RESULT_ENTERED)); // to be updated later paymentDto.setPaymentDate(Calendar.getInstance().getTime()); paymentDto.setBalance(amount); PaymentBL payment = new PaymentBL(); payment.create(paymentDto); // this updates the id // use the payment processor configured PluggableTaskManager taskManager = new PluggableTaskManager(entityId, Constants.PLUGGABLE_TASK_PAYMENT); PaymentTask task = (PaymentTask) taskManager.getNextClass(); boolean processNext = true; while (task != null && processNext) { processNext = task.preAuth(paymentDto); // get the next task task = (PaymentTask) taskManager.getNextClass(); // at the time, a pre-auth acts just like a normal payment for events AbstractPaymentEvent event = AbstractPaymentEvent.forPaymentResult(entityId, paymentDto); if (event != null) { EventManager.process(event); } } // update the result payment.getEntity().setPaymentResult(paymentDto.getPaymentResult()); //create the return value PaymentAuthorizationDTOEx retValue = new PaymentAuthorizationDTOEx(paymentDto.getAuthorization().getOldDTO()); if (paymentDto.getPaymentResult().getId() != Constants.RESULT_OK) { // if it was not successfull, it should not have balance payment.getEntity().setBalance(BigDecimal.ZERO); retValue.setResult(false); } else { retValue.setResult(true); } return retValue; } public static String get4digitExpiry(CreditCardDTO cc) { String expiry = null; GregorianCalendar cal = new GregorianCalendar(); cal.setTime(cc.getCcExpiry()); expiry = String.valueOf( cal.get(GregorianCalendar.MONTH) + 1) + String.valueOf( cal.get(GregorianCalendar.YEAR)).substring(2); if (expiry.length() == 3) { expiry = "0" + expiry; } return expiry; } /** * Deletes existing cc records and adds a new one. * @param executorId * Id of the user executing this method. * @param userId * Id of user who is updating cc. * @param cc * New cc data. */ public void updateForUser(Integer executorId, Integer userId, CreditCardDTO cc) { UserDTO user = UserBL.getUserEntity(userId); Iterator iter = user.getCreditCards().iterator(); // delete existing cc records while (iter.hasNext()) { set(((CreditCardDTO) iter.next()).getId()); delete(executorId); iter.remove(); } // add the new one create(cc); // creditCard.setUserId(userId); user.getCreditCards().add(getEntity()); getEntity().getBaseUsers().add(user); new UserDAS().save(user); new CreditCardDAS().save(getEntity()); } }