Search in sources :

Example 26 with InvoiceApiException

use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.

the class DefaultInvoiceDao method changeInvoiceStatus.

@Override
public void changeInvoiceStatus(final UUID invoiceId, final InvoiceStatus newStatus, final InternalCallContext context) throws InvoiceApiException {
    final List<Tag> invoicesTags = getInvoicesTags(context);
    transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<Void>() {

        @Override
        public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
            final InvoiceSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
            // Retrieve the invoice and make sure it belongs to the right account
            final InvoiceModelDao invoice = transactional.getById(invoiceId.toString(), context);
            if (invoice == null) {
                throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
            }
            if (invoice.getStatus().equals(newStatus)) {
                throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_STATUS, newStatus, invoiceId, invoice.getStatus());
            }
            transactional.updateStatus(invoiceId.toString(), newStatus.toString(), context);
            cbaDao.doCBAComplexityFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
            if (InvoiceStatus.COMMITTED.equals(newStatus)) {
                // notify invoice creation event
                notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoice, context);
            }
            return null;
        }
    });
}
Also used : InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) EntitySqlDaoWrapperFactory(org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory) Tag(org.killbill.billing.util.tag.Tag) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) EventBusException(org.killbill.bus.api.PersistentBus.EventBusException) EntityPersistenceException(org.killbill.billing.entity.EntityPersistenceException)

Example 27 with InvoiceApiException

use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.

the class InvoiceDaoHelper method createAdjustmentItem.

/**
     * Create an adjustment for a given invoice item. This just creates the object in memory, it doesn't write it to disk.
     *
     * @param invoiceId         the invoice id
     * @param invoiceItemId     the invoice item id to adjust
     * @param effectiveDate     adjustment effective date, in the account timezone
     * @param positiveAdjAmount the amount to adjust. Pass null to adjust the full amount of the original item
     * @param currency          the currency of the amount. Pass null to default to the original currency used
     * @return the adjustment item
     */
public InvoiceItemModelDao createAdjustmentItem(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID invoiceId, final UUID invoiceItemId, final BigDecimal positiveAdjAmount, final Currency currency, final LocalDate effectiveDate, final InternalCallContext context) throws InvoiceApiException {
    // First, retrieve the invoice item in question
    final InvoiceItemSqlDao invoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
    final InvoiceItemModelDao invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString(), context);
    if (invoiceItemToBeAdjusted == null) {
        throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
    }
    // Validate the invoice it belongs to
    if (!invoiceItemToBeAdjusted.getInvoiceId().equals(invoiceId)) {
        throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT, invoiceItemId, invoiceId);
    }
    // Retrieve the amount and currency if needed
    final BigDecimal amountToAdjust = MoreObjects.firstNonNull(positiveAdjAmount, invoiceItemToBeAdjusted.getAmount());
    // TODO - should we enforce the currency (and respect the original one) here if the amount passed was null?
    final Currency currencyForAdjustment = MoreObjects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
    // Note! The amount is negated here!
    return new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.ITEM_ADJ, invoiceItemToBeAdjusted.getInvoiceId(), invoiceItemToBeAdjusted.getAccountId(), null, null, null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
}
Also used : InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) Currency(org.killbill.billing.catalog.api.Currency) BigDecimal(java.math.BigDecimal)

Example 28 with InvoiceApiException

use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.

the class InvoiceDaoHelper method computePositiveRefundAmount.

public BigDecimal computePositiveRefundAmount(final InvoicePaymentModelDao payment, final BigDecimal requestedRefundAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
    final BigDecimal maxRefundAmount = payment.getAmount() == null ? BigDecimal.ZERO : payment.getAmount();
    final BigDecimal requestedPositiveAmount = requestedRefundAmount == null ? maxRefundAmount : requestedRefundAmount;
    // (But that should have been checked in the payment call already)
    if (requestedPositiveAmount.compareTo(maxRefundAmount) > 0) {
        throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_TOO_HIGH, requestedPositiveAmount, maxRefundAmount);
    }
    // Verify if the requested amount matches the invoice items to adjust, if specified
    BigDecimal amountFromItems = BigDecimal.ZERO;
    for (final BigDecimal itemAmount : invoiceItemIdsWithAmounts.values()) {
        amountFromItems = amountFromItems.add(itemAmount);
    }
    // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
    if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && requestedPositiveAmount.compareTo(amountFromItems) < 0) {
        throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST, requestedPositiveAmount, amountFromItems);
    }
    return requestedPositiveAmount;
}
Also used : InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) BigDecimal(java.math.BigDecimal)

Example 29 with InvoiceApiException

use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.

the class FixedAndRecurringInvoiceItemGenerator method processRecurringEvent.

// Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day)
private List<InvoiceItem> processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, final LocalDate targetDate, final Currency currency, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger, final BillingMode billingMode, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate, final InternalCallContext internalCallContext) throws InvoiceApiException {
    try {
        final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
        // For FIXEDTERM phases we need to stop when the specified duration has been reached
        final LocalDate maxEndDate = thisEvent.getPlanPhase().getPhaseType() == PhaseType.FIXEDTERM ? thisEvent.getPlanPhase().getDuration().addToLocalDate(internalCallContext.toLocalDate(thisEvent.getEffectiveDate())) : null;
        // Handle recurring items
        final BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
        if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
            final LocalDate startDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate());
            if (!startDate.isAfter(targetDate)) {
                final LocalDate endDate = (nextEvent == null) ? null : internalCallContext.toLocalDate(nextEvent.getEffectiveDate());
                final int billCycleDayLocal = thisEvent.getBillCycleDayLocal();
                final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate;
                try {
                    itemDataWithNextBillingCycleDate = generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode);
                } catch (final InvalidDateSequenceException e) {
                    throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
                }
                for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) {
                    // Stop if there a maxEndDate and we have reached it
                    if (maxEndDate != null && maxEndDate.compareTo(itemDatum.getEndDate()) < 0) {
                        break;
                    }
                    final BigDecimal rate = thisEvent.getRecurringPrice(internalCallContext.toUTCDateTime(itemDatum.getStartDate()));
                    if (rate != null) {
                        final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency);
                        final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(), thisEvent.getSubscription().getId(), thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(), itemDatum.getStartDate(), itemDatum.getEndDate(), amount, rate, currency);
                        items.add(recurringItem);
                    }
                }
                updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate);
            }
        }
        // For debugging purposes
        invoiceItemGeneratorLogger.append(thisEvent, items);
        return items;
    } catch (final CatalogApiException e) {
        throw new InvoiceApiException(e);
    }
}
Also used : FixedPriceInvoiceItem(org.killbill.billing.invoice.model.FixedPriceInvoiceItem) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) BillingPeriod(org.killbill.billing.catalog.api.BillingPeriod) InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod(org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) ArrayList(java.util.ArrayList) RecurringInvoiceItemData(org.killbill.billing.invoice.model.RecurringInvoiceItemData) LocalDate(org.joda.time.LocalDate) BigDecimal(java.math.BigDecimal) InvalidDateSequenceException(org.killbill.billing.invoice.model.InvalidDateSequenceException) RecurringInvoiceItemDataWithNextBillingCycleDate(org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException)

Example 30 with InvoiceApiException

use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.

the class FixedAndRecurringInvoiceItemGenerator method safetyBounds.

@VisibleForTesting
void safetyBounds(final Iterable<InvoiceItem> resultingItems, final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription, final InternalTenantContext internalCallContext) throws InvoiceApiException {
    // See https://github.com/killbill/killbill/issues/664
    if (config.isSanitySafetyBoundEnabled(internalCallContext)) {
        final Map<UUID, Multimap<LocalDate, InvoiceItem>> fixedItemsPerDateAndSubscription = new HashMap<UUID, Multimap<LocalDate, InvoiceItem>>();
        final Map<UUID, Multimap<Range<LocalDate>, InvoiceItem>> recurringItemsPerServicePeriodAndSubscription = new HashMap<UUID, Multimap<Range<LocalDate>, InvoiceItem>>();
        for (final InvoiceItem resultingItem : resultingItems) {
            if (resultingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
                if (fixedItemsPerDateAndSubscription.get(resultingItem.getSubscriptionId()) == null) {
                    fixedItemsPerDateAndSubscription.put(resultingItem.getSubscriptionId(), LinkedListMultimap.<LocalDate, InvoiceItem>create());
                }
                fixedItemsPerDateAndSubscription.get(resultingItem.getSubscriptionId()).put(resultingItem.getStartDate(), resultingItem);
                final Collection<InvoiceItem> resultingInvoiceItems = fixedItemsPerDateAndSubscription.get(resultingItem.getSubscriptionId()).get(resultingItem.getStartDate());
                if (resultingInvoiceItems.size() > 1) {
                    throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED Multiple FIXED items for subscriptionId='%s', startDate='%s', resultingItems=%s", resultingItem.getSubscriptionId(), resultingItem.getStartDate(), resultingInvoiceItems));
                }
            } else if (resultingItem.getInvoiceItemType() == InvoiceItemType.RECURRING) {
                if (recurringItemsPerServicePeriodAndSubscription.get(resultingItem.getSubscriptionId()) == null) {
                    recurringItemsPerServicePeriodAndSubscription.put(resultingItem.getSubscriptionId(), LinkedListMultimap.<Range<LocalDate>, InvoiceItem>create());
                }
                final Range<LocalDate> interval = Range.<LocalDate>closedOpen(resultingItem.getStartDate(), resultingItem.getEndDate());
                recurringItemsPerServicePeriodAndSubscription.get(resultingItem.getSubscriptionId()).put(interval, resultingItem);
                final Collection<InvoiceItem> resultingInvoiceItems = recurringItemsPerServicePeriodAndSubscription.get(resultingItem.getSubscriptionId()).get(interval);
                if (resultingInvoiceItems.size() > 1) {
                    throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED Multiple RECURRING items for subscriptionId='%s', startDate='%s', endDate='%s', resultingItems=%s", resultingItem.getSubscriptionId(), resultingItem.getStartDate(), resultingItem.getEndDate(), resultingInvoiceItems));
                }
            }
        }
    }
    // Trigger an exception if we create too many invoice items for a subscription on a given day
    if (config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext) == -1) {
        // Safety bound disabled
        return;
    }
    for (final InvoiceItem invoiceItem : resultingItems) {
        if (invoiceItem.getSubscriptionId() != null) {
            final LocalDate resultingItemCreationDay = trackInvoiceItemCreatedDay(invoiceItem, createdItemsPerDayPerSubscription, internalCallContext);
            final Collection<LocalDate> creationDaysForSubscription = createdItemsPerDayPerSubscription.get(invoiceItem.getSubscriptionId());
            int i = 0;
            for (final LocalDate creationDayForSubscription : creationDaysForSubscription) {
                if (creationDayForSubscription.compareTo(resultingItemCreationDay) == 0) {
                    i++;
                    if (i > config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext)) {
                        // Proposed items have already been logged
                        throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED subscriptionId='%s', resultingItem=%s", invoiceItem.getSubscriptionId(), invoiceItem));
                    }
                }
            }
        }
    }
}
Also used : FixedPriceInvoiceItem(org.killbill.billing.invoice.model.FixedPriceInvoiceItem) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) HashMap(java.util.HashMap) Range(com.google.common.collect.Range) LocalDate(org.joda.time.LocalDate) Multimap(com.google.common.collect.Multimap) LinkedListMultimap(com.google.common.collect.LinkedListMultimap) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) Collection(java.util.Collection) UUID(java.util.UUID) VisibleForTesting(com.google.common.annotations.VisibleForTesting)

Aggregations

InvoiceApiException (org.killbill.billing.invoice.api.InvoiceApiException)56 UUID (java.util.UUID)29 Invoice (org.killbill.billing.invoice.api.Invoice)26 InvoiceItem (org.killbill.billing.invoice.api.InvoiceItem)24 BigDecimal (java.math.BigDecimal)23 LocalDate (org.joda.time.LocalDate)23 DefaultInvoice (org.killbill.billing.invoice.model.DefaultInvoice)19 Test (org.testng.annotations.Test)16 InternalCallContext (org.killbill.billing.callcontext.InternalCallContext)14 FixedPriceInvoiceItem (org.killbill.billing.invoice.model.FixedPriceInvoiceItem)14 RecurringInvoiceItem (org.killbill.billing.invoice.model.RecurringInvoiceItem)14 LinkedList (java.util.LinkedList)13 ItemAdjInvoiceItem (org.killbill.billing.invoice.model.ItemAdjInvoiceItem)12 RepairAdjInvoiceItem (org.killbill.billing.invoice.model.RepairAdjInvoiceItem)12 BillingEventSet (org.killbill.billing.junction.BillingEventSet)9 AccountApiException (org.killbill.billing.account.api.AccountApiException)7 MockPlan (org.killbill.billing.catalog.MockPlan)7 MockPlanPhase (org.killbill.billing.catalog.MockPlanPhase)7 MockBillingEventSet (org.killbill.billing.invoice.MockBillingEventSet)7 SubscriptionFutureNotificationDates (org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates)7