/* 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 java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.log4j.Logger; import com.sapienter.jbilling.common.SessionInternalError; import com.sapienter.jbilling.server.invoice.db.InvoiceDAS; import com.sapienter.jbilling.server.payment.PaymentDTOEx; import com.sapienter.jbilling.server.pluggableTask.PluggableTask; import com.sapienter.jbilling.server.pluggableTask.admin.ParameterDescription; import com.sapienter.jbilling.server.process.ConfigurationBL; import com.sapienter.jbilling.server.user.UserBL; import com.sapienter.jbilling.server.user.UserDTOEx; import com.sapienter.jbilling.server.util.Constants; import com.sapienter.jbilling.server.util.audit.EventLogger; public class BasicSubscriptionStatusManagerTask extends PluggableTask implements ISubscriptionStatusManager { private static final Logger LOG = Logger.getLogger(BasicSubscriptionStatusManagerTask.class); public static final ParameterDescription PARAMETER_ITEM_TYPE_ID = new ParameterDescription("item_type_id", true, ParameterDescription.Type.STR); private PaymentDTOEx payment; private Integer entityId; //initializer for pluggable params { descriptions.add(PARAMETER_ITEM_TYPE_ID); } public void paymentFailed(Integer entityId, PaymentDTOEx payment) { this.payment = payment; this.entityId = entityId; if (!isPaymentApplicable(true)) { LOG.debug("This payment can't be processed " + payment); return; } LOG.debug("A payment failed " + payment); UserBL user = getUser(null); Integer status = user.getEntity().getSubscriberStatus().getId(); if (isLastRetry()) { if (status.equals(UserDTOEx.SUBSCRIBER_PENDING_EXPIRATION)) { user.updateSubscriptionStatus(UserDTOEx.SUBSCRIBER_EXPIRED); } else { LOG.warn("Last retry, but user not in pending expariation. Status = " + status); } } else { // not paying is not good if (status.equals(UserDTOEx.SUBSCRIBER_ACTIVE)) { user.updateSubscriptionStatus(UserDTOEx.SUBSCRIBER_PENDING_EXPIRATION); } else if (!status.equals(UserDTOEx.SUBSCRIBER_PENDING_EXPIRATION)) { LOG.warn("Not clear what to do with a customer in status " + status); } } } public void paymentSuccessful(Integer entityId, PaymentDTOEx payment) { this.payment = payment; this.entityId = entityId; if (!isPaymentApplicable(false)) { return; } UserBL user = getUser(null); // currently, any payment get's you to active, regardless of the amount. // hence, this is not supporting partial payments ... event a partial // payment will take you to active. user.updateSubscriptionStatus(UserDTOEx.SUBSCRIBER_ACTIVE); } public void subscriptionEnds(Integer userId, Date newActiveUntil, Date oldActiveUntil) { UserBL user = null; // it is known that both are different if (oldActiveUntil == null || (newActiveUntil != null && newActiveUntil.after(oldActiveUntil))) { user = getUser(userId); if (user.getEntity().getSubscriberStatus().getId() == UserDTOEx.SUBSCRIBER_ACTIVE) { user.updateSubscriptionStatus( UserDTOEx.SUBSCRIBER_PENDING_UNSUBSCRIPTION); } else { LOG.info("Should go to pending unsubscription, but is in " + user.getEntity().getSubscriberStatus().getDescription(1)); } } else if (newActiveUntil == null) { // it's going back to on-going (subscribed) user = getUser(userId); if (user.getEntity().getSubscriberStatus().getId() == UserDTOEx.SUBSCRIBER_PENDING_UNSUBSCRIPTION) { user.updateSubscriptionStatus( UserDTOEx.SUBSCRIBER_ACTIVE); } else { LOG.info("Should go to active, but is in " + user.getEntity().getSubscriberStatus().getDescription(1)); } } } public void subscriptionEnds(Integer userId, Date date) { UserBL user = getUser(userId); if (!user.isCurrentlySubscribed(date)) { user.updateSubscriptionStatus(UserDTOEx.SUBSCRIBER_UNSUBSCRIBED); } } private boolean isPaymentApplicable(boolean failed) { // no payment? then it's not applicable if( payment == null ) return false; // If a payment is a refund, then it's not applicable if( payment.getIsRefund() != 0 ) return false; // failed payments that don't have an "attempt" set (is this some kind of counter?) // don't apply if( failed && payment.getAttempt() == null ) return false; String typeStr = (String) parameters.get(PARAMETER_ITEM_TYPE_ID.getName()); if (typeStr == null || typeStr.length() == 0) { throw new SessionInternalError("parameter " + PARAMETER_ITEM_TYPE_ID.getName() + " is required"); } boolean retValue = false; if (payment.getInvoiceIds() == null || payment.getInvoiceIds().size() == 0) { // If you don't have an invoice, it doesn't change subscription status, // unless it's a preauthorization. // We're assuming that ALL preauthorizations without invoices are being // used to start a subscription. boolean isPreAuth = (payment.getIsPreauth() != null) && (payment.getIsPreauth() != 0); return isPreAuth; // don't bother looking for the item category (you can't get to it anyway, this is a "naked" pre-auth payment). } else { // validate that this payment is for a subscription item for(Integer invoiceId : payment.getInvoiceIds()) { if (new InvoiceDAS().isReleatedToItemType(invoiceId, Integer.valueOf(typeStr))) { retValue = true; } } } if (retValue == false) { new EventLogger().auditBySystem(entityId, payment.getUserId(), Constants.TABLE_BASE_USER, payment.getUserId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.SUBSCRIPTION_STATUS_NO_CHANGE, payment.getId(), typeStr, null); LOG.debug("Payment did not change subscription status to active." + "Invoice with item category " + typeStr + " not found"); } return retValue; } private boolean isLastRetry() { ConfigurationBL config = null; try { config = new ConfigurationBL(entityId); } catch (Exception e) { throw new SessionInternalError("Processing payment to change status", BasicSubscriptionStatusManagerTask.class, e); } // it is the number of retries plus one for the initial process if (payment.getAttempt().intValue() >= config.getEntity().getRetries().intValue() + 1) { // return true; } else { return false; } } private UserBL getUser(Integer userId) { // find the user, and its status UserBL user = null; try { if (userId == null) { user = new UserBL(payment.getUserId()); } else { user = new UserBL(userId); } } catch (Exception e) { throw new SessionInternalError(e); } return user; } }