/*
* Copyright 2010-2013 Ning, Inc.
* Copyright 2014-2017 Groupon, Inc
* Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.killbill.billing.invoice;
import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.events.BlockingTransitionInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.UserType;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
public class InvoiceListener {
private static final Logger log = LoggerFactory.getLogger(InvoiceListener.class);
private final InvoiceDispatcher dispatcher;
private final InternalCallContextFactory internalCallContextFactory;
private final AccountInternalApi accountApi;
private final InvoiceInternalApi invoiceApi;
private final InvoiceConfig invoiceConfig;
private final Clock clock;
@Inject
public InvoiceListener(final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory,
final InvoiceConfig invoiceConfig, final InvoiceDispatcher dispatcher, InvoiceInternalApi invoiceApi) {
this.accountApi = accountApi;
this.dispatcher = dispatcher;
this.invoiceConfig = invoiceConfig;
this.internalCallContextFactory = internalCallContextFactory;
this.clock = clock;
this.invoiceApi = invoiceApi;
}
@AllowConcurrentEvents
@Subscribe
public void handleSubscriptionTransition(final EffectiveSubscriptionInternalEvent event) {
try {
// Skip future uncancel event
// Skip events which are marked as not being the last one
if (event.getTransitionType() == SubscriptionBaseTransitionType.UNCANCEL ||
event.getRemainingEventsForUserOperation() > 0) {
return;
}
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
dispatcher.processSubscriptionForInvoiceGeneration(event, context);
} catch (InvoiceApiException e) {
log.warn("Unable to process event {}", event, e);
}
}
@AllowConcurrentEvents
@Subscribe
public void handleBlockingStateTransition(final BlockingTransitionInternalEvent event) {
// We are only interested in blockBilling or unblockBilling transitions.
if (!event.isTransitionedToUnblockedBilling() && !event.isTransitionedToBlockedBilling()) {
return;
}
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
final UUID accountId = accountApi.getByRecordId(event.getSearchKey1(), context);
dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, context);
} catch (InvoiceApiException e) {
log.warn("Unable to process event {}", event, e);
} catch (AccountApiException e) {
log.warn("Unable to process event {}", event, e);
}
}
public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
dispatcher.processSubscriptionForInvoiceGeneration(subscriptionId, context.toLocalDate(eventDateTime), context);
} catch (InvoiceApiException e) {
log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
}
}
public void handleEventForInvoiceNotification(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
dispatcher.processSubscriptionForInvoiceNotification(subscriptionId, context.toLocalDate(eventDateTime), context);
} catch (InvoiceApiException e) {
log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
}
}
@AllowConcurrentEvents
@Subscribe
public void handleChildrenInvoiceCreationEvent(final InvoiceCreationInternalEvent event) {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "CreateParentInvoice", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
final Account account = accountApi.getAccountById(event.getAccountId(), context);
// catch children invoices and populate the parent summary invoice
if (isChildrenAccountAndPaymentDelegated(account)) {
dispatcher.processParentInvoiceForInvoiceGeneration(account, event.getInvoiceId(), context);
}
} catch (InvoiceApiException e) {
log.error(e.getMessage());
} catch (AccountApiException e) {
log.error(e.getMessage());
}
}
private boolean isChildrenAccountAndPaymentDelegated(final Account account) {
return account.getParentAccountId() != null && account.isPaymentDelegatedToParent();
}
public void handleParentInvoiceCommitmentEvent(final UUID invoiceId, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Commit Invoice", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
invoiceApi.commitInvoice(invoiceId, context);
} catch (InvoiceApiException e) {
log.error(e.getMessage());
}
}
@AllowConcurrentEvents
@Subscribe
public void handleChildrenInvoiceAdjustmentEvent(final DefaultInvoiceAdjustmentEvent event) {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "AdjustParentInvoice", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
final Account account = accountApi.getAccountById(event.getAccountId(), context);
// catch children invoices and populate the parent summary invoice
if (isChildrenAccountAndPaymentDelegated(account)) {
dispatcher.processParentInvoiceForAdjustments(account, event.getInvoiceId(), context);
}
} catch (InvoiceApiException e) {
log.error(e.getMessage());
} catch (AccountApiException e) {
log.error(e.getMessage());
}
}
}