use of org.killbill.billing.invoice.model.ExternalChargeInvoiceItem in project killbill by killbill.
the class TestDefaultInvoiceUserApi method testPostExternalChargeForBundleOnExistingInvoice.
@Test(groups = "slow")
public void testPostExternalChargeForBundleOnExistingInvoice() throws Exception {
// Verify the initial invoice balance
final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId, callContext).getBalance();
Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
// Verify the initial account balance
final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
Assert.assertEquals(accountBalance, invoiceBalance);
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
final UUID bundleId = UUID.randomUUID();
final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
verifyExternalChargeOnExistingInvoice(invoiceBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
}
use of org.killbill.billing.invoice.model.ExternalChargeInvoiceItem in project killbill by killbill.
the class TestIntegrationInvoice method testExternalChargeInTheFuture.
@Test(groups = "slow")
public void testExternalChargeInTheFuture() throws Exception {
final int billingDay = 1;
final DateTime initialCreationDate = new DateTime(2019, 1, 1, 0, 0, 0, 0, testTimeZone);
// set clock to the initial start date
clock.setTime(initialCreationDate);
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
// Create external charge with an effective date a year from now
final LocalDate chargeEffectiveDate = clock.getToday(account.getTimeZone()).plusYears(1);
final List<InvoiceItem> invoiceItemList = new ArrayList<InvoiceItem>();
ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(null, account.getId(), null, "", chargeEffectiveDate, chargeEffectiveDate, BigDecimal.TEN, account.getCurrency(), null);
invoiceItemList.add(item);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final List<InvoiceItem> chargeItems = invoiceUserApi.insertExternalCharges(account.getId(), chargeEffectiveDate, invoiceItemList, true, null, callContext);
assertListenerStatus();
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
expectedInvoices.add(new ExpectedInvoiceItemCheck(chargeEffectiveDate, chargeEffectiveDate, InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2019, 1, 1), new LocalDate(2019, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
}
use of org.killbill.billing.invoice.model.ExternalChargeInvoiceItem in project killbill by killbill.
the class DefaultInvoiceUserApi method insertItems.
private List<InvoiceItem> insertItems(final UUID accountId, final LocalDate effectiveDate, final InvoiceItemType itemType, final Iterable<InvoiceItem> inputItems, final boolean autoCommit, final LinkedList<PluginProperty> properties, final CallContext context) throws InvoiceApiException {
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(accountId, context);
ImmutableAccountData accountData;
try {
accountData = accountUserApi.getImmutableAccountDataById(accountId, internalTenantContext);
} catch (AccountApiException e) {
throw new InvoiceApiException(e);
}
final Currency accountCurrency = accountData.getCurrency();
final WithAccountLock withAccountLock = new WithAccountLock() {
@Override
public Iterable<DefaultInvoice> prepareInvoices() throws InvoiceApiException {
final LocalDate invoiceDate = internalTenantContext.toLocalDate(context.getCreatedDate());
final Map<UUID, DefaultInvoice> newAndExistingInvoices = new HashMap<UUID, DefaultInvoice>();
UUID newInvoiceId = null;
for (final InvoiceItem inputItem : inputItems) {
if (inputItem.getAmount() == null || inputItem.getAmount().compareTo(BigDecimal.ZERO) < 0) {
if (itemType == InvoiceItemType.EXTERNAL_CHARGE) {
throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID, inputItem.getAmount());
} else if (itemType == InvoiceItemType.CREDIT_ADJ) {
throw new InvoiceApiException(ErrorCode.CREDIT_AMOUNT_INVALID, inputItem.getAmount());
}
}
if (inputItem.getCurrency() != null && !inputItem.getCurrency().equals(accountCurrency)) {
throw new InvoiceApiException(ErrorCode.CURRENCY_INVALID, inputItem.getCurrency(), accountCurrency);
}
final UUID invoiceIdForItem = inputItem.getInvoiceId();
final Invoice curInvoiceForItem;
if (invoiceIdForItem == null) {
final Currency currency = inputItem.getCurrency();
final InvoiceStatus status = autoCommit ? InvoiceStatus.COMMITTED : InvoiceStatus.DRAFT;
if (newInvoiceId == null) {
final DefaultInvoice newInvoiceForItems = new DefaultInvoice(accountId, invoiceDate, effectiveDate, currency, status);
newInvoiceId = newInvoiceForItems.getId();
newAndExistingInvoices.put(newInvoiceId, newInvoiceForItems);
}
curInvoiceForItem = newAndExistingInvoices.get(newInvoiceId);
} else {
if (newAndExistingInvoices.get(invoiceIdForItem) == null) {
final DefaultInvoice existingInvoiceForExternalCharge = getInvoiceInternal(invoiceIdForItem, context);
switch(existingInvoiceForExternalCharge.getStatus()) {
case COMMITTED:
throw new InvoiceApiException(ErrorCode.INVOICE_ALREADY_COMMITTED, existingInvoiceForExternalCharge.getId());
case VOID:
// TODO Add missing error https://github.com/killbill/killbill/issues/1501
throw new IllegalStateException(String.format("Cannot add credit or external charge for invoice id %s because it is in \" + InvoiceStatus.VOID + \" status\"", existingInvoiceForExternalCharge.getId()));
case DRAFT:
default:
break;
}
newAndExistingInvoices.put(invoiceIdForItem, existingInvoiceForExternalCharge);
}
curInvoiceForItem = newAndExistingInvoices.get(invoiceIdForItem);
}
final InvoiceItem newInvoiceItem;
switch(itemType) {
case EXTERNAL_CHARGE:
newInvoiceItem = new ExternalChargeInvoiceItem(UUIDs.randomUUID(), context.getCreatedDate(), curInvoiceForItem.getId(), accountId, inputItem.getBundleId(), inputItem.getSubscriptionId(), inputItem.getProductName(), inputItem.getPlanName(), inputItem.getPhaseName(), inputItem.getPrettyProductName(), inputItem.getPrettyPlanName(), inputItem.getPrettyPhaseName(), inputItem.getDescription(), MoreObjects.firstNonNull(inputItem.getStartDate(), effectiveDate), inputItem.getEndDate(), inputItem.getAmount(), inputItem.getRate(), accountCurrency, inputItem.getLinkedItemId(), inputItem.getQuantity(), inputItem.getItemDetails());
break;
case CREDIT_ADJ:
newInvoiceItem = new CreditAdjInvoiceItem(UUIDs.randomUUID(), context.getCreatedDate(), curInvoiceForItem.getId(), accountId, effectiveDate, inputItem.getDescription(), // Note! The amount is negated here!
inputItem.getAmount().negate(), inputItem.getRate(), inputItem.getCurrency(), inputItem.getQuantity(), inputItem.getItemDetails());
break;
case TAX:
newInvoiceItem = new TaxInvoiceItem(UUIDs.randomUUID(), curInvoiceForItem.getId(), accountId, inputItem.getBundleId(), inputItem.getDescription(), MoreObjects.firstNonNull(inputItem.getStartDate(), effectiveDate), inputItem.getAmount(), accountCurrency);
break;
default:
throw new IllegalStateException(String.format("Unsupported to add item of type '%s'", itemType));
}
curInvoiceForItem.addInvoiceItem(newInvoiceItem);
}
return newAndExistingInvoices.values();
}
};
return invoiceApiHelper.dispatchToInvoicePluginsAndInsertItems(accountId, false, withAccountLock, properties, context);
}
use of org.killbill.billing.invoice.model.ExternalChargeInvoiceItem in project killbill by killbill.
the class TestWithInvoicePlugin method testBasicAdditionalExternalChargeItem.
@Test(groups = "slow")
public void testBasicAdditionalExternalChargeItem() throws Exception {
// We take april as it has 30 days (easier to play with BCD)
// Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
clock.setDay(new LocalDate(2012, 4, 1));
final AccountData accountData = getAccountData(1);
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
final UUID pluginInvoiceItemId = UUID.randomUUID();
final UUID pluginLinkedItemId = UUID.randomUUID();
testInvoicePluginApi.additionalInvoiceItem = new ExternalChargeInvoiceItem(pluginInvoiceItemId, clock.getUTCNow(), null, account.getId(), null, null, null, null, null, null, null, null, "My charge", clock.getUTCToday(), null, BigDecimal.TEN, null, Currency.USD, pluginLinkedItemId, null);
Assert.assertEquals(testInvoicePluginApi.invocationCount, 0);
// Create original subscription (Trial PHASE) -> $0 invoice but plugin added one item
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")), new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
Assert.assertEquals(testInvoicePluginApi.invocationCount, 1);
final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
final InvoiceItem externalCharge = Iterables.tryFind(invoiceItems, new Predicate<InvoiceItem>() {
@Override
public boolean apply(final InvoiceItem input) {
return input.getInvoiceItemType() == InvoiceItemType.EXTERNAL_CHARGE;
}
}).orNull();
assertNotNull(externalCharge);
// verify the ID is the one passed by the plugin #818
assertEquals(externalCharge.getId(), pluginInvoiceItemId);
// verify the ID is the one passed by the plugin #887
assertEquals(externalCharge.getLinkedItemId(), pluginLinkedItemId);
// On next invoice we will update the amount and the description of the previously inserted EXTERNAL_CHARGE item
testInvoicePluginApi.additionalInvoiceItem = new ExternalChargeInvoiceItem(pluginInvoiceItemId, clock.getUTCNow(), invoices.get(0).getId(), account.getId(), null, null, null, null, null, null, null, null, "Update Description", clock.getUTCToday(), null, BigDecimal.ONE, null, Currency.USD, pluginLinkedItemId, null);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addDays(30);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
final List<Invoice> invoices2 = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
final List<InvoiceItem> invoiceItems2 = invoices2.get(0).getInvoiceItems();
final InvoiceItem externalCharge2 = Iterables.tryFind(invoiceItems2, new Predicate<InvoiceItem>() {
@Override
public boolean apply(final InvoiceItem input) {
return input.getInvoiceItemType() == InvoiceItemType.EXTERNAL_CHARGE;
}
}).orNull();
assertNotNull(externalCharge2);
assertEquals(externalCharge2.getAmount().compareTo(BigDecimal.ONE), 0);
}
use of org.killbill.billing.invoice.model.ExternalChargeInvoiceItem in project killbill by killbill.
the class TestOverdueIntegration method testShouldNotBeInOverdueAfterDraftExternalCharge.
@Test(groups = "slow", description = "Test overdue for draft external charge", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testShouldNotBeInOverdueAfterDraftExternalCharge() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
setupAccount();
// Create a subscription without failing payments
final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
// 2012-05-06 => Create an external charge on a new invoice
addDaysAndCheckForCompletion(5);
final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), BigDecimal.TEN, Currency.USD, null);
invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), false, null, callContext).get(0);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
// 2012-05-31 => DAY 30 have to get out of trial before first payment
addDaysAndCheckForCompletion(25, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
// Should still be in clear state - the invoice for the bundle has been paid, but not the invoice with the external charge (because it is in draft mode)
// We refresh overdue just to be safe, see below
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
// 2012-06-06 => Past 30 days since the external charge
addDaysAndCheckForCompletion(6);
// We should still be clear
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
Assert.assertEquals(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), null, clock.getUTCToday(), callContext).size(), 0);
}
Aggregations