killbill-memoizeit
Changes
beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java 2(+1 -1)
invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java 81(+79 -2)
invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java 21(+15 -6)
invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java 151(+151 -0)
Details
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
index 9ef4144..7a063cc 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
@@ -19,5 +19,12 @@ package com.ning.billing.invoice.api.formatters;
import com.ning.billing.invoice.api.Invoice;
public interface InvoiceFormatter extends Invoice {
+
public String getFormattedInvoiceDate();
+
+ public String getFormattedChargedAmount();
+
+ public String getFormattedPaidAmount();
+
+ public String getFormattedBalance();
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
index 7175fde..2927744 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
@@ -19,7 +19,10 @@ package com.ning.billing.invoice.api.formatters;
import com.ning.billing.invoice.api.InvoiceItem;
public interface InvoiceItemFormatter extends InvoiceItem {
+
public String getFormattedStartDate();
public String getFormattedEndDate();
+
+ public String getFormattedAmount();
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index fa58913..1d63e9d 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -57,6 +57,14 @@ public interface InvoiceUserApi {
public BigDecimal getAccountBalance(UUID accountId);
/**
+ * Retrieve the account CBA.
+ *
+ * @param accountId account id
+ * @return the account CBA
+ */
+ public BigDecimal getAccountCBA(UUID accountId);
+
+ /**
* Retrieve an invoice by id.
*
* @param invoiceId invoice id
@@ -260,6 +268,17 @@ public interface InvoiceUserApi {
BigDecimal amount, Currency currency, CallContext context) throws InvoiceApiException;
/**
+ * Delete a CBA item.
+ *
+ * @param accountId account id
+ * @param invoiceId invoice id
+ * @param invoiceItemId invoice item id (must be of type CBA_ADJ)
+ * @param context the call context
+ * @throws InvoiceApiException
+ */
+ public void deleteCBA(UUID accountId, UUID invoiceId, UUID invoiceItemId, CallContext context) throws InvoiceApiException;
+
+ /**
* Retrieve the invoice formatted in HTML.
*
* @param invoiceId invoice id
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java b/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java
index dda0dd6..850dcf7 100644
--- a/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
*
* Ning 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
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java
index 773a1eb..27de0ba 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
*
* Ning 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
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
index 618162b..11c5e8d 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
*
* Ning 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
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 11efa03..6e05ad7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
@@ -50,6 +49,8 @@ import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
+import com.google.inject.Inject;
+
public class DefaultInvoiceUserApi implements InvoiceUserApi {
private final InvoiceDao dao;
@@ -90,6 +91,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
+ public BigDecimal getAccountCBA(final UUID accountId) {
+ final BigDecimal result = dao.getAccountCBA(accountId);
+ return result == null ? BigDecimal.ZERO : result;
+ }
+
+ @Override
public Invoice getInvoice(final UUID invoiceId) throws InvoiceApiException {
return dao.getById(invoiceId);
}
@@ -219,6 +226,11 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
+ public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+ dao.deleteCBA(accountId, invoiceId, invoiceItemId, context);
+ }
+
+ @Override
public String getInvoiceAsHTML(final UUID invoiceId) throws AccountApiException, IOException, InvoiceApiException {
final Invoice invoice = getInvoice(invoiceId);
if (invoice == null) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
index 923bd25..08db235 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
@@ -35,12 +35,6 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableMap.Builder;
-import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
@@ -72,6 +66,14 @@ import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.dao.TableName;
import com.ning.billing.util.tag.ControlTagType;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+
public class AuditedInvoiceDao implements InvoiceDao {
private static final Logger log = LoggerFactory.getLogger(AuditedInvoiceDao.class);
@@ -579,7 +581,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
@Override
public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId, final String description,
final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context)
- throws InvoiceApiException {
+ throws InvoiceApiException {
return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
@Override
public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -673,6 +675,90 @@ public class AuditedInvoiceDao implements InvoiceDao {
}
@Override
+ public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+ invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+ @Override
+ public Void inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+ // Retrieve the invoice and make sure it belongs to the right account
+ final Invoice invoice = transactional.getById(invoiceId.toString());
+ if (invoice == null || !invoice.getAccountId().equals(accountId)) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
+ }
+
+ // Retrieve the invoice item and make sure it belongs to the right invoice
+ final InvoiceItemSqlDao invoiceItemSqlDao = transactional.become(InvoiceItemSqlDao.class);
+ final InvoiceItem cbaItem = invoiceItemSqlDao.getById(invoiceItemId.toString());
+ if (cbaItem == null || !cbaItem.getInvoiceId().equals(invoice.getId())) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+ }
+
+ // First, adjust the same invoice with the CBA amount to "delete"
+ final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+ cbaItem.getId(), cbaItem.getAmount().negate(), cbaItem.getCurrency());
+ invoiceItemSqlDao.create(cbaAdjItem, context);
+
+ // If there is more account credit than CBA we adjusted, we're done.
+ // Otherwise, we need to find further invoices on which this credit was consumed
+ final BigDecimal accountCBA = getAccountCBAFromTransaction(accountId, invoiceSqlDao);
+ if (accountCBA.compareTo(BigDecimal.ZERO) < 0) {
+ if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
+ throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
+ }
+ final List<Invoice> invoicesFollowing = transactional.getInvoicesByAccountAfterDate(accountId.toString(),
+ invoice.getInvoiceDate().toDateTimeAtStartOfDay().toDate());
+ populateChildren(invoicesFollowing, transactional);
+
+ // The remaining amount to adjust (i.e. the amount of credits used on following invoices)
+ // is the current account CBA balance (minus the sign)
+ BigDecimal positiveRemainderToAdjust = accountCBA.negate();
+ for (final Invoice invoiceFollowing : invoicesFollowing) {
+ if (invoiceFollowing.getId().equals(invoice.getId())) {
+ continue;
+ }
+
+ // Add a single adjustment per invoice
+ BigDecimal positiveCBAAdjItemAmount = BigDecimal.ZERO;
+
+ // Start with the most recent invoice to limit the impact on overdue
+ for (final InvoiceItem cbaUsed : Lists.reverse(invoiceFollowing.getInvoiceItems())) {
+ // Ignore non CBA items or credits (CBA >= 0)
+ if (!InvoiceItemType.CBA_ADJ.equals(cbaUsed.getInvoiceItemType()) ||
+ cbaUsed.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+ continue;
+ }
+
+ final BigDecimal positiveCBAUsedAmount = cbaUsed.getAmount().negate();
+ final BigDecimal positiveNextCBAAdjItemAmount;
+ if (positiveCBAUsedAmount.compareTo(positiveRemainderToAdjust) < 0) {
+ positiveNextCBAAdjItemAmount = positiveCBAUsedAmount;
+ positiveRemainderToAdjust = positiveRemainderToAdjust.min(positiveNextCBAAdjItemAmount);
+ } else {
+ positiveNextCBAAdjItemAmount = positiveRemainderToAdjust;
+ positiveRemainderToAdjust = BigDecimal.ZERO;
+ }
+ positiveCBAAdjItemAmount = positiveCBAAdjItemAmount.add(positiveNextCBAAdjItemAmount);
+
+ if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
+ break;
+ }
+ }
+
+ // Add the adjustment on that invoice
+ final InvoiceItem nextCBAAdjItem = new CreditBalanceAdjInvoiceItem(invoiceFollowing.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+ cbaItem.getId(), positiveCBAAdjItemAmount, cbaItem.getCurrency());
+ invoiceItemSqlDao.create(nextCBAAdjItem, context);
+ if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
+ break;
+ }
+ }
+ }
+
+ return null;
+ }
+ });
+ }
+
+ @Override
public void test() {
invoiceSqlDao.test();
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 207c1de..936fc83 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -158,4 +158,13 @@ public interface InvoiceDao {
*/
InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate,
@Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context);
+
+ /**
+ * Delete a CBA item.
+ *
+ * @param accountId the account id
+ * @param invoiceId the invoice id
+ * @param invoiceItemId the invoice item id of the cba item to delete
+ */
+ void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 2391bc9..b990922 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -143,7 +143,7 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
item = new RecurringInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
break;
case CBA_ADJ:
- item = new CreditBalanceAdjInvoiceItem(id, invoiceId, accountId, startDate, amount, currency);
+ item = new CreditBalanceAdjInvoiceItem(id, invoiceId, accountId, startDate, linkedItemId, amount, currency);
break;
case CREDIT_ADJ:
item = new CreditAdjInvoiceItem(id, invoiceId, accountId, startDate, amount, currency);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index c1e3140..c07f13b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -57,7 +57,6 @@ import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
import com.ning.billing.junction.api.BillingEventSet;
import com.ning.billing.util.clock.Clock;
-import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.inject.Inject;
@@ -82,9 +81,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
*/
@Override
public Invoice generateInvoice(final UUID accountId, @Nullable final BillingEventSet events,
- @Nullable final List<Invoice> existingInvoices,
- final LocalDate targetDate, final DateTimeZone accountTimeZone,
- final Currency targetCurrency) throws InvoiceApiException {
+ @Nullable final List<Invoice> existingInvoices,
+ final LocalDate targetDate, final DateTimeZone accountTimeZone,
+ final Currency targetCurrency) throws InvoiceApiException {
if ((events == null) || (events.size() == 0) || events.isAccountAutoInvoiceOff()) {
return null;
}
@@ -98,8 +97,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
for (final Invoice invoice : existingInvoices) {
for (final InvoiceItem item : invoice.getInvoiceItems()) {
if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charges etc.
- !events.getSubscriptionIdsWithAutoInvoiceOff()
- .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
+ !events.getSubscriptionIdsWithAutoInvoiceOff()
+ .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
existingItems.add(item);
}
}
@@ -131,7 +130,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
void generateCBAForExistingInvoices(final UUID accountId, final List<Invoice> existingInvoices,
- final List<InvoiceItem> proposedItems, final Currency currency) {
+ final List<InvoiceItem> proposedItems, final Currency currency) {
// Determine most accurate invoice balances up to this point
final Map<UUID, BigDecimal> amountOwedByInvoice = new HashMap<UUID, BigDecimal>();
@@ -163,7 +162,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
void addRepairedItems(final List<InvoiceItem> existingItems, final List<InvoiceItem> proposedItems) {
for (final InvoiceItem existingItem : existingItems) {
if (existingItem.getInvoiceItemType() == InvoiceItemType.RECURRING ||
- existingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
+ existingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
final BigDecimal existingAdjustedPositiveAmount = getAdjustedPositiveAmount(existingItems, existingItem.getId());
final BigDecimal amountNegated = existingItem.getAmount() == null ? null : existingItem.getAmount().subtract(existingAdjustedPositiveAmount).negate();
if (amountNegated.compareTo(BigDecimal.ZERO) < 0) {
@@ -175,7 +174,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
-
// We check to see if there are any adjustments that point to the item we are trying to repair
// If we did any CREDIT_ADJ or REFUND_ADJ, then we unfortunately we can't know what is the intent
// was as it applies to the full Invoice, so we ignore it. That might result in an extra positive CBA
@@ -188,7 +186,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
@Override
public boolean apply(InvoiceItem item) {
if (item.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ &&
- item.getLinkedItemId() != null && item.getLinkedItemId().equals(linkedItemId)) {
+ item.getLinkedItemId() != null && item.getLinkedItemId().equals(linkedItemId)) {
return true;
} else {
return false;
@@ -204,7 +202,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
void consumeExistingCredit(final UUID invoiceId, final UUID accountId, final List<InvoiceItem> existingItems,
- final List<InvoiceItem> proposedItems, final Currency targetCurrency) {
+ final List<InvoiceItem> proposedItems, final Currency targetCurrency) {
BigDecimal totalUnusedCreditAmount = BigDecimal.ZERO;
BigDecimal totalAmountOwed = BigDecimal.ZERO;
@@ -266,7 +264,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
* Removes all matching items from both submitted collections
*/
void removeDuplicatedInvoiceItems(final List<InvoiceItem> proposedItems,
- final List<InvoiceItem> existingInvoiceItems) {
+ final List<InvoiceItem> existingInvoiceItems) {
// We can't just use sets here as order matters (we want to keep duplicated in existingInvoiceItems)
final Iterator<InvoiceItem> proposedItemIterator = proposedItems.iterator();
while (proposedItemIterator.hasNext()) {
@@ -305,7 +303,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final UUID accountId, final BillingEventSet events,
- final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
+ final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
if (events.size() == 0) {
@@ -343,7 +341,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
// 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> processEvents(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent,
- final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
+ final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
// Handle fixed price items
@@ -377,13 +375,13 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
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);
+ accountId,
+ thisEvent.getSubscription().getBundleId(),
+ thisEvent.getSubscription().getId(),
+ thisEvent.getPlan().getName(),
+ thisEvent.getPlanPhase().getName(),
+ itemDatum.getStartDate(), itemDatum.getEndDate(),
+ amount, rate, currency);
items.add(recurringItem);
}
}
@@ -396,15 +394,15 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
private BillingMode instantiateBillingMode(final BillingModeType billingMode) {
switch (billingMode) {
- case IN_ADVANCE:
- return new InAdvanceBillingMode();
- default:
- throw new UnsupportedOperationException();
+ case IN_ADVANCE:
+ return new InAdvanceBillingMode();
+ default:
+ throw new UnsupportedOperationException();
}
}
InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
- final LocalDate targetDate, final Currency currency) {
+ final LocalDate targetDate, final Currency currency) {
final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone());
if (roundedStartDate.isAfter(targetDate)) {
@@ -414,9 +412,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
if (fixedPrice != null) {
return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(),
- thisEvent.getSubscription().getId(),
- thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
- roundedStartDate, fixedPrice, currency);
+ thisEvent.getSubscription().getId(),
+ thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+ roundedStartDate, fixedPrice, currency);
} else {
return null;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
index 6c856e8..3b43340 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
@@ -19,13 +19,13 @@ package com.ning.billing.invoice.model;
import java.math.BigDecimal;
import java.util.UUID;
-import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItemType;
public class CreditAdjInvoiceItem extends AdjInvoiceItem {
+
public CreditAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
final BigDecimal amount, final Currency currency) {
super(invoiceId, accountId, date, date, amount, currency);
@@ -43,6 +43,6 @@ public class CreditAdjInvoiceItem extends AdjInvoiceItem {
@Override
public String getDescription() {
- return "credit-adj";
+ return "Invoice adjustment";
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
index 3eb05b2..588a650 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
@@ -25,14 +25,21 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItemType;
public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
+
public CreditBalanceAdjInvoiceItem(final UUID invoiceId, final UUID accountId,
final LocalDate date, final BigDecimal amount, final Currency currency) {
super(invoiceId, accountId, date, date, amount, currency);
}
public CreditBalanceAdjInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId,
- final LocalDate date, final BigDecimal amount, final Currency currency) {
- super(id, invoiceId, accountId, date, date, amount, currency);
+ final LocalDate date, final UUID linkedInvoiceItemId,
+ final BigDecimal amount, final Currency currency) {
+ super(id, invoiceId, accountId, date, date, amount, currency, linkedInvoiceItemId);
+ }
+
+ public CreditBalanceAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date, final UUID linkedInvoiceItemId,
+ final BigDecimal amount, final Currency currency) {
+ super(invoiceId, accountId, date, date, amount, currency, linkedInvoiceItemId);
}
@Override
@@ -42,6 +49,12 @@ public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
@Override
public String getDescription() {
- return "cba-adj";
+ final String secondDescription;
+ if (getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+ secondDescription = "account credit";
+ } else {
+ secondDescription = "use of account credit";
+ }
+ return String.format("Adjustment (%s)", secondDescription);
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
index c8f0245..44c915e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -41,7 +41,11 @@ public class ExternalChargeInvoiceItem extends InvoiceItemBase {
@Override
public String getDescription() {
- return String.format("%s (external charge) on %s", getPlanName(), getStartDate().toString());
+ if (getPlanName() == null) {
+ return "External charge";
+ } else {
+ return String.format("%s (external charge)", getPlanName());
+ }
}
@Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index f2d641e..d169d78 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -28,6 +28,7 @@ import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceItemType;
public class FixedPriceInvoiceItem extends InvoiceItemBase {
+
public FixedPriceInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId, final String planName, final String phaseName,
final LocalDate date, final BigDecimal amount, final Currency currency) {
super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, date, null, amount, currency);
@@ -40,7 +41,15 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
@Override
public String getDescription() {
- return String.format("%s (fixed price) on %s", getPhaseName(), getStartDate().toString());
+ if (getPhaseName() == null) {
+ return "Fixed price charge";
+ } else {
+ if (getAmount().compareTo(BigDecimal.ZERO) == 0) {
+ return getPhaseName();
+ } else {
+ return String.format("%s (fixed price)", getPhaseName());
+ }
+ }
}
@Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
index cbfe229..5392adf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
@@ -45,6 +45,6 @@ public class ItemAdjInvoiceItem extends AdjInvoiceItem {
@Override
public String getDescription() {
- return "item-adj";
+ return "Invoice item adjustment";
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index d6f98a2..c3f4afa 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -50,7 +50,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
@Override
public String getDescription() {
- return String.format("%s from %s to %s", phaseName, startDate.toString(dateTimeFormatter), endDate.toString(dateTimeFormatter));
+ return phaseName;
}
@Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
index 9091b6c..6ccef1e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItemType;
public class RefundAdjInvoiceItem extends AdjInvoiceItem {
+
public RefundAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
final BigDecimal amount, final Currency currency) {
super(invoiceId, accountId, date, date, amount, currency);
@@ -42,6 +43,6 @@ public class RefundAdjInvoiceItem extends AdjInvoiceItem {
@Override
public String getDescription() {
- return "refund-adj";
+ return "Invoice adjustment";
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
index 36327d2..1b3155b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItemType;
public class RepairAdjInvoiceItem extends AdjInvoiceItem {
+
public RepairAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate startDate, final LocalDate endDate,
final BigDecimal amount, final Currency currency, final UUID reversingId) {
super(invoiceId, accountId, startDate, endDate, amount, currency, reversingId);
@@ -42,6 +43,6 @@ public class RepairAdjInvoiceItem extends AdjInvoiceItem {
@Override
public String getDescription() {
- return "repair-adj";
+ return "Adjustment (entitlement change)";
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index f9ef212..b6edb8d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -33,6 +33,7 @@
package com.ning.billing.invoice.template.formatters;
import java.math.BigDecimal;
+import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -45,8 +46,11 @@ import org.joda.time.format.DateTimeFormatter;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.util.template.translation.TranslatorConfig;
import com.google.common.base.Objects;
@@ -56,7 +60,7 @@ import com.google.common.collect.ImmutableList;
import static com.ning.billing.invoice.template.formatters.DefaultAmountFormatter.round;
/**
- * Format invoice fields. Note that the Mustache engine won't accept null values.
+ * Format invoice fields
*/
public class DefaultInvoiceFormatter implements InvoiceFormatter {
@@ -79,13 +83,68 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
@Override
public List<InvoiceItem> getInvoiceItems() {
- final List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+ final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+
+ InvoiceItem mergedCBAItem = null;
+ InvoiceItem mergedInvoiceAdjustment = null;
for (final InvoiceItem item : invoice.getInvoiceItems()) {
+ if (InvoiceItemType.CBA_ADJ.equals(item.getInvoiceItemType())) {
+ // Merge CBA items to avoid confusing the customer, since these are internal
+ // adjustments (auto generated)
+ mergedCBAItem = mergeCBAItem(invoiceItems, mergedCBAItem, item);
+ } else if (InvoiceItemType.REFUND_ADJ.equals(item.getInvoiceItemType()) ||
+ InvoiceItemType.CREDIT_ADJ.equals(item.getInvoiceItemType())) {
+ // Merge refund adjustments and credit adjustments, as these are both
+ // the same for the customer (invoice adjustment)
+ mergedInvoiceAdjustment = mergeInvoiceAdjustmentItem(invoiceItems, mergedInvoiceAdjustment, item);
+ } else {
+ invoiceItems.add(item);
+ }
+ }
+ if (mergedCBAItem != null) {
+ invoiceItems.add(mergedCBAItem);
+ }
+ if (mergedInvoiceAdjustment != null) {
+ invoiceItems.add(mergedInvoiceAdjustment);
+ }
+
+ final List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+ for (final InvoiceItem item : invoiceItems) {
formatters.add(new DefaultInvoiceItemFormatter(config, item, dateFormatter, locale));
}
return formatters;
}
+ private InvoiceItem mergeCBAItem(final List<InvoiceItem> invoiceItems, InvoiceItem mergedCBAItem, final InvoiceItem item) {
+ if (mergedCBAItem == null) {
+ mergedCBAItem = item;
+ } else {
+ // This is really just to be safe - they should always have the same currency
+ if (!mergedCBAItem.getCurrency().equals(item.getCurrency())) {
+ invoiceItems.add(item);
+ } else {
+ mergedCBAItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), invoice.getInvoiceDate(),
+ mergedCBAItem.getAmount().add(item.getAmount()), mergedCBAItem.getCurrency());
+ }
+ }
+ return mergedCBAItem;
+ }
+
+ private InvoiceItem mergeInvoiceAdjustmentItem(final List<InvoiceItem> invoiceItems, InvoiceItem mergedInvoiceAdjustment, final InvoiceItem item) {
+ if (mergedInvoiceAdjustment == null) {
+ mergedInvoiceAdjustment = item;
+ } else {
+ // This is really just to be safe - they should always have the same currency
+ if (!mergedInvoiceAdjustment.getCurrency().equals(item.getCurrency())) {
+ invoiceItems.add(item);
+ } else {
+ mergedInvoiceAdjustment = new CreditAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), invoice.getInvoiceDate(),
+ mergedInvoiceAdjustment.getAmount().add(item.getAmount()), mergedInvoiceAdjustment.getCurrency());
+ }
+ }
+ return mergedInvoiceAdjustment;
+ }
+
@Override
public boolean addInvoiceItem(final InvoiceItem item) {
return invoice.addInvoiceItem(item);
@@ -147,6 +206,24 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
}
@Override
+ public String getFormattedChargedAmount() {
+ final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+ return number.format(getChargedAmount().doubleValue());
+ }
+
+ @Override
+ public String getFormattedPaidAmount() {
+ final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+ return number.format(getPaidAmount().doubleValue());
+ }
+
+ @Override
+ public String getFormattedBalance() {
+ final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+ return number.format(getBalance().doubleValue());
+ }
+
+ @Override
public boolean isMigrationInvoice() {
return invoice.isMigrationInvoice();
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 04982f4..b16a17e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -17,15 +17,13 @@
package com.ning.billing.invoice.template.formatters;
import java.math.BigDecimal;
+import java.text.NumberFormat;
import java.util.Locale;
import java.util.UUID;
-import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
-import com.google.common.base.Objects;
-import com.google.common.base.Strings;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceItemType;
@@ -34,12 +32,16 @@ import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
import com.ning.billing.util.template.translation.Translator;
import com.ning.billing.util.template.translation.TranslatorConfig;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+
import static com.ning.billing.invoice.template.formatters.DefaultAmountFormatter.round;
/**
- * Format invoice item fields. Note that the Mustache engine won't accept null values.
+ * Format invoice item fields
*/
public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
+
private final Translator translator;
private final InvoiceItem item;
@@ -65,6 +67,13 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
}
@Override
+ public String getFormattedAmount() {
+ final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+ number.setCurrency(java.util.Currency.getInstance(item.getCurrency().toString()));
+ return number.format(getAmount().doubleValue());
+ }
+
+ @Override
public InvoiceItemType getInvoiceItemType() {
return item.getInvoiceItemType();
}
@@ -86,12 +95,12 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
@Override
public String getFormattedStartDate() {
- return Strings.nullToEmpty(item.getStartDate().toString(dateFormatter));
+ return item.getStartDate().toString(dateFormatter);
}
@Override
public String getFormattedEndDate() {
- return Strings.nullToEmpty(item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter));
+ return item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter);
}
@Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
index 7f55be3..9d105e5 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -28,6 +28,7 @@ import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
import com.ning.billing.invoice.template.translator.DefaultInvoiceTranslator;
+import com.ning.billing.util.LocaleUtils;
import com.ning.billing.util.email.templates.TemplateEngine;
import com.ning.billing.util.template.translation.TranslatorConfig;
@@ -51,7 +52,7 @@ public class HtmlInvoiceGenerator {
final Map<String, Object> data = new HashMap<String, Object>();
final DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
- final Locale locale = new Locale(account.getLocale());
+ final Locale locale = LocaleUtils.toLocale(account.getLocale());
invoiceTranslator.setLocale(locale);
data.put("text", invoiceTranslator);
data.put("account", account);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 5a6b3a4..b45869d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -300,4 +300,9 @@ public class MockInvoiceDao implements InvoiceDao {
throws InvoiceApiException {
return null;
}
+
+ @Override
+ public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
index bc28352..54fce43 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
@@ -1319,4 +1319,169 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
tags = tagDao.loadEntities(invoice.getId(), ObjectType.INVOICE);
assertEquals(tags.size(), 0);
}
+
+ @Test(groups = "slow")
+ public void testDeleteCBANotConsumed() throws Exception {
+ final UUID accountId = UUID.randomUUID();
+
+ // Create invoice 1
+ // Scenario: single item with payment
+ // * $10 item
+ // Then, a repair occur:
+ // * $-10 repair
+ // * $10 generated CBA due to the repair (assume previous payment)
+ final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+ final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+ fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+ fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+ fixedItem1.getId());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+ fixedItem1.getStartDate(), fixedItem1.getAmount(),
+ fixedItem1.getCurrency());
+ invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+ invoiceItemSqlDao.create(fixedItem1, context);
+ invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+ invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+ // Verify scenario - no CBA should have been used
+ Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 10.00);
+ verifyInvoice(invoice1.getId(), 10.00, 10.00);
+
+ // Delete the CBA on invoice 1
+ invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+ // Verify the result
+ Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+ verifyInvoice(invoice1.getId(), 0.00, 0.00);
+ }
+
+ @Test(groups = "slow")
+ public void testDeleteCBAPartiallyConsumed() throws Exception {
+ final UUID accountId = UUID.randomUUID();
+
+ // Create invoice 1
+ // Scenario: single item with payment
+ // * $10 item
+ // Then, a repair occur:
+ // * $-10 repair
+ // * $10 generated CBA due to the repair (assume previous payment)
+ final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+ final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+ fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+ fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+ fixedItem1.getId());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+ fixedItem1.getStartDate(), fixedItem1.getAmount(),
+ fixedItem1.getCurrency());
+ invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+ invoiceItemSqlDao.create(fixedItem1, context);
+ invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+ invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+ // Create invoice 2
+ // Scenario: single item
+ // * $5 item
+ // * $-5 CBA used
+ final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
+ fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
+ fixedItem2.getCurrency());
+ invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
+ invoiceItemSqlDao.create(fixedItem2, context);
+ invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem2, context);
+
+ // Verify scenario - half of the CBA should have been used
+ Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 5.00);
+ verifyInvoice(invoice1.getId(), 10.00, 10.00);
+ verifyInvoice(invoice2.getId(), 0.00, -5.00);
+
+ // Delete the CBA on invoice 1
+ invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+ // Verify all three invoices were affected
+ Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+ verifyInvoice(invoice1.getId(), 0.00, 0.00);
+ verifyInvoice(invoice2.getId(), 5.00, 0.00);
+ }
+
+ @Test(groups = "slow")
+ public void testDeleteCBAFullyConsumedTwice() throws Exception {
+ final UUID accountId = UUID.randomUUID();
+
+ // Create invoice 1
+ // Scenario: single item with payment
+ // * $10 item
+ // Then, a repair occur:
+ // * $-10 repair
+ // * $10 generated CBA due to the repair (assume previous payment)
+ final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+ final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+ fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+ fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+ fixedItem1.getId());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+ fixedItem1.getStartDate(), fixedItem1.getAmount(),
+ fixedItem1.getCurrency());
+ invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+ invoiceItemSqlDao.create(fixedItem1, context);
+ invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+ invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+ // Create invoice 2
+ // Scenario: single item
+ // * $5 item
+ // * $-5 CBA used
+ final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
+ fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
+ fixedItem2.getCurrency());
+ invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
+ invoiceItemSqlDao.create(fixedItem2, context);
+ invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem2, context);
+
+ // Create invoice 3
+ // Scenario: single item
+ // * $5 item
+ // * $-5 CBA used
+ final DefaultInvoice invoice3 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem3 = new CreditBalanceAdjInvoiceItem(fixedItem3.getInvoiceId(), fixedItem3.getAccountId(),
+ fixedItem3.getStartDate(), fixedItem3.getAmount().negate(),
+ fixedItem3.getCurrency());
+ invoiceDao.create(invoice3, invoice3.getTargetDate().getDayOfMonth(), true, context);
+ invoiceItemSqlDao.create(fixedItem3, context);
+ invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem3, context);
+
+ // Verify scenario - all CBA should have been used
+ Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+ verifyInvoice(invoice1.getId(), 10.00, 10.00);
+ verifyInvoice(invoice2.getId(), 0.00, -5.00);
+ verifyInvoice(invoice3.getId(), 0.00, -5.00);
+
+ // Delete the CBA on invoice 1
+ invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+ // Verify all three invoices were affected
+ Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+ verifyInvoice(invoice1.getId(), 0.00, 0.00);
+ verifyInvoice(invoice2.getId(), 5.00, 0.00);
+ verifyInvoice(invoice3.getId(), 5.00, 0.00);
+ }
+
+ private void verifyInvoice(final UUID invoiceId, final double balance, final double cbaAmount) throws InvoiceApiException {
+ final Invoice invoice = invoiceDao.getById(invoiceId);
+ Assert.assertEquals(invoice.getBalance().doubleValue(), balance);
+ Assert.assertEquals(invoice.getCBAAmount().doubleValue(), cbaAmount);
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
index 1fac6f7..5fc759d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
@@ -108,7 +108,7 @@ public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
Assert.assertEquals(adjustedInvoiceItem.getAccountId(), invoiceItem.getAccountId());
Assert.assertNull(adjustedInvoiceItem.getBundleId());
Assert.assertEquals(adjustedInvoiceItem.getCurrency(), invoiceItem.getCurrency());
- Assert.assertEquals(adjustedInvoiceItem.getDescription(), "item-adj");
+ Assert.assertEquals(adjustedInvoiceItem.getDescription(), "Invoice item adjustment");
Assert.assertEquals(adjustedInvoiceItem.getEndDate(), effectiveDate);
Assert.assertEquals(adjustedInvoiceItem.getInvoiceId(), invoiceItem.getInvoiceId());
Assert.assertEquals(adjustedInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
new file mode 100644
index 0000000..0f12045
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.template.formatters;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.InvoiceTestSuite;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
+import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+public class TestDefaultInvoiceFormatter extends InvoiceTestSuite {
+
+ private TranslatorConfig config;
+ private MustacheTemplateEngine templateEngine;
+
+ @BeforeSuite(groups = "fast")
+ public void setup() {
+ config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+ templateEngine = new MustacheTemplateEngine();
+ }
+
+ @Test(groups = "fast")
+ public void testMergeItems() throws Exception {
+ // Scenario: single item with payment
+ // * $10 item
+ // Then, a repair occur:
+ // * $-10 repair
+ // * $10 generated CBA due to the repair (assume previous payment)
+ // Then, the invoice is adjusted for $1:
+ // * $-1 credit adjustment
+ // * $1 generated CBA due to the credit adjustment
+ // Then, we refund $1 with invoice level adjustment:
+ // * $-1 refund adjustment
+ final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), BigDecimal.TEN, Currency.USD);
+ final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), fixedItem.getEndDate(),
+ fixedItem.getAmount().negate(), fixedItem.getCurrency(),
+ fixedItem.getId());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), fixedItem.getAmount(),
+ fixedItem.getCurrency());
+ final CreditAdjInvoiceItem creditAdjInvoiceItem = new CreditAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), BigDecimal.ONE.negate(), fixedItem.getCurrency());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), creditAdjInvoiceItem.getAmount().negate(),
+ fixedItem.getCurrency());
+ final RefundAdjInvoiceItem refundAdjInvoiceItem = new RefundAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), BigDecimal.ONE.negate(), fixedItem.getCurrency());
+ final Invoice invoice = new DefaultInvoice(fixedItem.getInvoiceId(), fixedItem.getAccountId(), null,
+ new LocalDate(), new LocalDate(), Currency.USD, false);
+ invoice.addInvoiceItem(fixedItem);
+ invoice.addInvoiceItem(repairAdjInvoiceItem);
+ invoice.addInvoiceItem(creditBalanceAdjInvoiceItem);
+ invoice.addInvoiceItem(creditAdjInvoiceItem);
+ invoice.addInvoiceItem(creditBalanceAdjInvoiceItem2);
+ invoice.addInvoiceItem(refundAdjInvoiceItem);
+ // Check the scenario
+ Assert.assertEquals(invoice.getBalance().doubleValue(), 9.00);
+ Assert.assertEquals(invoice.getCBAAmount().doubleValue(), 11.00);
+ Assert.assertEquals(invoice.getRefundAdjAmount().doubleValue(), -1.00);
+
+ // Verify the merge
+ final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US);
+ final List<InvoiceItem> invoiceItems = formatter.getInvoiceItems();
+ Assert.assertEquals(invoiceItems.size(), 4);
+ Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ Assert.assertEquals(invoiceItems.get(0).getAmount().doubleValue(), 10.00);
+ Assert.assertEquals(invoiceItems.get(1).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+ Assert.assertEquals(invoiceItems.get(1).getAmount().doubleValue(), -10.00);
+ Assert.assertEquals(invoiceItems.get(2).getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
+ Assert.assertEquals(invoiceItems.get(2).getAmount().doubleValue(), 11.00);
+ Assert.assertEquals(invoiceItems.get(3).getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+ Assert.assertEquals(invoiceItems.get(3).getAmount().doubleValue(), -2.00);
+ }
+
+ @Test(groups = "fast")
+ public void testFormattedAmount() throws Exception {
+ final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
+ final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR);
+ invoiceEUR.addInvoiceItem(fixedItemEUR);
+
+ checkOutput(invoiceEUR,
+ "<tr>\n" +
+ " <td class=\"amount\"><strong>{{invoice.formattedChargedAmount}}</strong></td>\n" +
+ "</tr>\n" +
+ "<tr>\n" +
+ " <td class=\"amount\"><strong>{{invoice.formattedPaidAmount}}</strong></td>\n" +
+ "</tr>\n" +
+ "<tr>\n" +
+ " <td class=\"amount\"><strong>{{invoice.formattedBalance}}</strong></td>\n" +
+ "</tr>",
+ "<tr>\n" +
+ " <td class=\"amount\"><strong>1 499,95 €</strong></td>\n" +
+ "</tr>\n" +
+ "<tr>\n" +
+ " <td class=\"amount\"><strong>0,00 €</strong></td>\n" +
+ "</tr>\n" +
+ "<tr>\n" +
+ " <td class=\"amount\"><strong>1 499,95 €</strong></td>\n" +
+ "</tr>",
+ Locale.FRANCE);
+ }
+
+ private void checkOutput(final Invoice invoice, final String template, final String expected, final Locale locale) {
+ final Map<String, Object> data = new HashMap<String, Object>();
+ data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale));
+
+ final String formattedText = templateEngine.executeTemplateText(template, data);
+ Assert.assertEquals(formattedText, expected);
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
new file mode 100644
index 0000000..e91f9be
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.template.formatters;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.InvoiceTestSuite;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.LocaleUtils;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuite {
+
+ private TranslatorConfig config;
+ private MustacheTemplateEngine templateEngine;
+
+ @BeforeSuite(groups = "fast")
+ public void setup() {
+ config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+ templateEngine = new MustacheTemplateEngine();
+ }
+
+ @Test(groups = "fast")
+ public void testBasicUSD() throws Exception {
+ final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
+ checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+ "<td class=\"amount\">($1,114.75)</td>", LocaleUtils.toLocale("en_US"));
+ }
+
+ @Test(groups = "fast")
+ public void testFormattedAmount() throws Exception {
+ final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
+ checkOutput(fixedItemEUR, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+ "<td class=\"amount\">1 499,95 €</td>", Locale.FRANCE);
+
+ final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
+ checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}", "<td class=\"amount\">($1,114.75)</td>");
+
+ // Check locale/currency mismatch (locale is set at the account level)
+ final FixedPriceInvoiceItem fixedItemGBP = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), new BigDecimal("8.07"), Currency.GBP);
+ checkOutput(fixedItemGBP, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+ "<td class=\"amount\">8,07 GBP</td>", Locale.FRANCE);
+ }
+
+ @Test(groups = "fast")
+ public void testNullEndDate() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 12, 1);
+ final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ startDate, BigDecimal.TEN, Currency.USD);
+ checkOutput(fixedItem,
+ "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
+ "<td>Dec 1, 2012</td>");
+ }
+
+ @Test(groups = "fast")
+ public void testNonNullEndDate() throws Exception {
+ final LocalDate startDate = new LocalDate(2012, 12, 1);
+ final LocalDate endDate = new LocalDate(2012, 12, 31);
+ final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, Currency.USD);
+ checkOutput(recurringItem,
+ "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
+ "<td>Dec 1, 2012 - Dec 31, 2012</td>");
+ }
+
+ private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected) {
+ checkOutput(invoiceItem, template, expected, Locale.US);
+ }
+
+ private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected, final Locale locale) {
+ final Map<String, Object> data = new HashMap<String, Object>();
+ data.put("invoiceItem", new DefaultInvoiceItemFormatter(config, invoiceItem, DateTimeFormat.mediumDate(), locale));
+
+ final String formattedText = templateEngine.executeTemplateText(template, data);
+ Assert.assertEquals(formattedText, expected);
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java
index 618f924..5235fcb 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java
@@ -13,14 +13,15 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
+
package com.ning.billing.jaxrs.json;
import java.math.BigDecimal;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.ning.billing.account.api.Account;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
public class AccountJsonWithBalance extends AccountJson {
@@ -33,29 +34,29 @@ public class AccountJsonWithBalance extends AccountJson {
@JsonCreator
public AccountJsonWithBalance(@JsonProperty("accountId") final String accountId,
- @JsonProperty("name") final String name,
- @JsonProperty("firstNameLength") final Integer length,
- @JsonProperty("externalKey") final String externalKey,
- @JsonProperty("email") final String email,
- @JsonProperty("billCycleDay") final BillCycleDayJson billCycleDay,
- @JsonProperty("currency") final String currency,
- @JsonProperty("paymentMethodId") final String paymentMethodId,
- @JsonProperty("timezone") final String timeZone,
- @JsonProperty("address1") final String address1,
- @JsonProperty("address2") final String address2,
- @JsonProperty("postalCode") final String postalCode,
- @JsonProperty("company") final String company,
- @JsonProperty("city") final String city,
- @JsonProperty("state") final String state,
- @JsonProperty("country") final String country,
- @JsonProperty("locale") final String locale,
- @JsonProperty("phone") final String phone,
- @JsonProperty("isMigrated") final Boolean isMigrated,
- @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
- @JsonProperty("accountBalance") final BigDecimal accountBalance) {
- super(accountId, name, length, externalKey, email, billCycleDay, currency, paymentMethodId, timeZone,
- address1, address2, postalCode, company, city, state, country, locale, phone, isMigrated, isNotifiedForInvoices);
- this.accountBalance = accountBalance;
+ @JsonProperty("name") final String name,
+ @JsonProperty("firstNameLength") final Integer length,
+ @JsonProperty("externalKey") final String externalKey,
+ @JsonProperty("email") final String email,
+ @JsonProperty("billCycleDay") final BillCycleDayJson billCycleDay,
+ @JsonProperty("currency") final String currency,
+ @JsonProperty("paymentMethodId") final String paymentMethodId,
+ @JsonProperty("timezone") final String timeZone,
+ @JsonProperty("address1") final String address1,
+ @JsonProperty("address2") final String address2,
+ @JsonProperty("postalCode") final String postalCode,
+ @JsonProperty("company") final String company,
+ @JsonProperty("city") final String city,
+ @JsonProperty("state") final String state,
+ @JsonProperty("country") final String country,
+ @JsonProperty("locale") final String locale,
+ @JsonProperty("phone") final String phone,
+ @JsonProperty("isMigrated") final Boolean isMigrated,
+ @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
+ @JsonProperty("accountBalance") final BigDecimal accountBalance) {
+ super(accountId, name, length, externalKey, email, billCycleDay, currency, paymentMethodId, timeZone,
+ address1, address2, postalCode, company, city, state, country, locale, phone, isMigrated, isNotifiedForInvoices);
+ this.accountBalance = accountBalance;
}
public BigDecimal getAccountBalance() {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalanceAndCBA.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalanceAndCBA.java
new file mode 100644
index 0000000..17f0acb
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalanceAndCBA.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+
+import com.ning.billing.account.api.Account;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class AccountJsonWithBalanceAndCBA extends AccountJsonWithBalance {
+
+ private final BigDecimal accountCBA;
+
+ public AccountJsonWithBalanceAndCBA(final Account account, final BigDecimal accountBalance, final BigDecimal accountCBA) {
+ super(account, accountBalance);
+ this.accountCBA = accountCBA;
+ }
+
+ @JsonCreator
+ public AccountJsonWithBalanceAndCBA(@JsonProperty("accountId") final String accountId,
+ @JsonProperty("name") final String name,
+ @JsonProperty("firstNameLength") final Integer length,
+ @JsonProperty("externalKey") final String externalKey,
+ @JsonProperty("email") final String email,
+ @JsonProperty("billCycleDay") final BillCycleDayJson billCycleDay,
+ @JsonProperty("currency") final String currency,
+ @JsonProperty("paymentMethodId") final String paymentMethodId,
+ @JsonProperty("timezone") final String timeZone,
+ @JsonProperty("address1") final String address1,
+ @JsonProperty("address2") final String address2,
+ @JsonProperty("postalCode") final String postalCode,
+ @JsonProperty("company") final String company,
+ @JsonProperty("city") final String city,
+ @JsonProperty("state") final String state,
+ @JsonProperty("country") final String country,
+ @JsonProperty("locale") final String locale,
+ @JsonProperty("phone") final String phone,
+ @JsonProperty("isMigrated") final Boolean isMigrated,
+ @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
+ @JsonProperty("accountBalance") final BigDecimal accountBalance,
+ @JsonProperty("accountCBA") final BigDecimal accountCBA) {
+ super(accountId, name, length, externalKey, email, billCycleDay, currency, paymentMethodId, timeZone, address1,
+ address2, postalCode, company, city, state, country, locale, phone, isMigrated, isNotifiedForInvoices, accountBalance);
+ this.accountCBA = accountCBA;
+ }
+
+ public BigDecimal getAccountCBA() {
+ return accountCBA;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index ee4a6fc..6d186d3 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -60,6 +60,7 @@ import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.jaxrs.json.AccountEmailJson;
import com.ning.billing.jaxrs.json.AccountJson;
import com.ning.billing.jaxrs.json.AccountJsonWithBalance;
+import com.ning.billing.jaxrs.json.AccountJsonWithBalanceAndCBA;
import com.ning.billing.jaxrs.json.AccountTimelineJson;
import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
import com.ning.billing.jaxrs.json.CustomFieldJson;
@@ -136,17 +137,10 @@ public class AccountResource extends JaxRsResourceBase {
@Path("/{accountId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
public Response getAccount(@PathParam("accountId") final String accountId,
- @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance) throws AccountApiException {
+ @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
+ @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA) throws AccountApiException {
final Account account = accountApi.getAccountById(UUID.fromString(accountId));
-
- AccountJson json = null;
- if (accountWithBalance) {
- final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId());
- json = new AccountJsonWithBalance(account, accountBalance);
- } else {
- json = new AccountJson(account);
- }
- return Response.status(Status.OK).entity(json).build();
+ return getAccount(account, accountWithBalance, accountWithBalanceAndCBA);
}
@GET
@@ -177,16 +171,24 @@ public class AccountResource extends JaxRsResourceBase {
@GET
@Produces(APPLICATION_JSON)
public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
- @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance) throws AccountApiException {
+ @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
+ @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA) throws AccountApiException {
final Account account = accountApi.getAccountByKey(externalKey);
- AccountJson json = null;
- if (accountWithBalance) {
+ return getAccount(account, accountWithBalance, accountWithBalanceAndCBA);
+ }
+
+ private Response getAccount(final Account account, final Boolean accountWithBalance, final Boolean accountWithBalanceAndCBA) {
+ final AccountJson json;
+ if (accountWithBalanceAndCBA) {
+ final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId());
+ final BigDecimal accountCBA = invoiceApi.getAccountCBA(account.getId());
+ json = new AccountJsonWithBalanceAndCBA(account, accountBalance, accountCBA);
+ } else if (accountWithBalance) {
final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId());
json = new AccountJsonWithBalance(account, accountBalance);
} else {
json = new AccountJson(account);
}
-
return Response.status(Status.OK).entity(json).build();
}
@@ -214,7 +216,7 @@ public class AccountResource extends JaxRsResourceBase {
final AccountData data = json.toAccountData();
final UUID uuid = UUID.fromString(accountId);
accountApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment));
- return getAccount(accountId, false);
+ return getAccount(accountId, false, false);
}
// Not supported
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index aafebdb..0f3531b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -18,6 +18,7 @@ package com.ning.billing.jaxrs.resources;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -180,6 +181,24 @@ public class InvoiceResource extends JaxRsResourceBase {
}
}
+ @DELETE
+ @Path("/{invoiceId:" + UUID_PATTERN + "}" + "/{invoiceItemId:" + UUID_PATTERN + "}/cba")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response deleteCBA(@PathParam("invoiceId") final String invoiceId,
+ @PathParam("invoiceItemId") final String invoiceItemId,
+ @QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, InvoiceApiException {
+ final Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+ invoiceApi.deleteCBA(account.getId(), UUID.fromString(invoiceId), UUID.fromString(invoiceItemId),
+ context.createContext(createdBy, reason, comment));
+
+ return Response.status(Status.OK).build();
+ }
+
@POST
@Path("/{invoiceId:" + UUID_PATTERN + "}")
@Consumes(APPLICATION_JSON)
@@ -314,6 +333,30 @@ public class InvoiceResource extends JaxRsResourceBase {
@POST
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
+ @Path("/" + PAYMENTS)
+ public Response payAllInvoices(final PaymentJsonSimple payment,
+ @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, PaymentApiException {
+ final Account account = accountApi.getAccountById(UUID.fromString(payment.getAccountId()));
+
+ final CallContext callContext = context.createContext(createdBy, reason, comment);
+ final Collection<Invoice> unpaidInvoices = invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday());
+ for (final Invoice invoice : unpaidInvoices) {
+ if (externalPayment) {
+ paymentApi.createExternalPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+ } else {
+ paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+ }
+ }
+
+ return Response.status(Status.OK).build();
+ }
+
+ @POST
+ @Produces(APPLICATION_JSON)
+ @Consumes(APPLICATION_JSON)
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
public Response createInstantPayment(final PaymentJsonSimple payment,
@QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 56f7140..9c8af32 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -51,6 +51,7 @@ public interface JaxrsResource {
public static final String QUERY_POLICY = "policy";
public static final String QUERY_ACCOUNT_WITH_BALANCE = "accountWithBalance";
+ public static final String QUERY_ACCOUNT_WITH_BALANCE_AND_CBA = "accountWithBalanceAndCBA";
public static final String QUERY_ACCOUNT_ID = "accountId";
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 712baeb..000c896 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -86,6 +86,30 @@ public class TestInvoice extends TestJaxrsBase {
}
@Test(groups = "slow")
+ public void testPayAllInvoices() throws Exception {
+ clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
+
+ // No payment method
+ final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // Check there was no payment made
+ assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 0);
+
+ // Get the invoices
+ final List<InvoiceJsonSimple> invoices = getInvoicesForAccount(accountJson.getAccountId());
+ assertEquals(invoices.size(), 2);
+ final InvoiceJsonSimple invoiceToPay = invoices.get(1);
+ assertEquals(invoiceToPay.getBalance().compareTo(BigDecimal.ZERO), 1);
+
+ // Pay all invoices
+ payAllInvoices(accountJson, true);
+ for (final InvoiceJsonSimple invoice : getInvoicesForAccount(accountJson.getAccountId())) {
+ assertEquals(invoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+ }
+ assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 1);
+ }
+
+ @Test(groups = "slow")
public void testInvoiceCreatePayment() throws Exception {
clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 0426343..ab631f2 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -784,6 +784,15 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
return objFromJson;
}
+ protected void payAllInvoices(final AccountJson accountJson, final Boolean externalPayment) throws IOException {
+ final PaymentJsonSimple payment = new PaymentJsonSimple(null, null, accountJson.getAccountId(), null, null, null, null,
+ null, 0, null, null, null, null, null, null, null);
+ final String postJson = mapper.writeValueAsString(payment);
+
+ final String uri = JaxrsResource.INVOICES_PATH + "/" + JaxrsResource.PAYMENTS;
+ doPost(uri, postJson, ImmutableMap.<String, String>of("externalPayment", externalPayment.toString()), DEFAULT_HTTP_TIMEOUT_SEC);
+ }
+
protected List<PaymentJsonSimple> createInstaPayment(final AccountJson accountJson, final InvoiceJsonSimple invoice) throws IOException {
final PaymentJsonSimple payment = new PaymentJsonSimple(invoice.getAmount(), BigDecimal.ZERO, accountJson.getAccountId(),
invoice.getInvoiceId(), null, null, null, null, 0, null, null, null, null, null, null, null);
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
index f6a76a3..8ddc2aa 100644
--- a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
+++ b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
@@ -23,13 +23,21 @@ import java.util.Map;
import com.ning.billing.util.config.UriAccessor;
import com.ning.billing.util.io.IOUtils;
+
+import com.google.common.annotations.VisibleForTesting;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
public class MustacheTemplateEngine implements TemplateEngine {
+
@Override
public String executeTemplate(final String templateName, final Map<String, Object> data) throws IOException {
final String templateText = getTemplateText(templateName);
+ return executeTemplateText(templateText, data);
+ }
+
+ @VisibleForTesting
+ public String executeTemplateText(final String templateText, final Map<String, Object> data) {
final Template template = Mustache.compiler().compile(templateText);
return template.execute(data);
}
diff --git a/util/src/main/java/com/ning/billing/util/LocaleUtils.java b/util/src/main/java/com/ning/billing/util/LocaleUtils.java
new file mode 100644
index 0000000..cbf1c13
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/LocaleUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.util;
+
+import java.util.Locale;
+
+public class LocaleUtils {
+
+ private LocaleUtils() {
+ }
+
+ // From commons-lang
+ public static Locale toLocale(final String str) {
+ if (str == null) {
+ return null;
+ }
+ final int len = str.length();
+ if (len != 2 && len != 5 && len < 7) {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ if (len == 2) {
+ return new Locale(str, "");
+ } else {
+ if (str.charAt(2) != '_') {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ final char ch3 = str.charAt(3);
+ if (ch3 == '_') {
+ return new Locale(str.substring(0, 2), "", str.substring(4));
+ }
+ final char ch4 = str.charAt(4);
+ if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ if (len == 5) {
+ return new Locale(str.substring(0, 2), str.substring(3, 5));
+ } else {
+ if (str.charAt(5) != '_') {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
+ }
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
index 72d3d90..d21e5ad 100644
--- a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
+import com.ning.billing.util.LocaleUtils;
import com.ning.billing.util.config.UriAccessor;
public abstract class DefaultTranslatorBase implements Translator {
@@ -60,7 +61,7 @@ public abstract class DefaultTranslatorBase implements Translator {
return originalText;
}
- final Locale defaultLocale = new Locale(config.getDefaultLocale());
+ final Locale defaultLocale = LocaleUtils.toLocale(config.getDefaultLocale());
try {
bundle = getBundle(defaultLocale, bundlePath);