
org.killbill.billing.invoice.dao.DefaultInvoiceDao Maven / Gradle / Ivy
/*
* Copyright 2010-2013 Ning, Inc.
* Copyright 2014-2016 Groupon, Inc
* Copyright 2014-2016 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.killbill.billing.invoice.dao;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.DefaultInvoicePaymentErrorEvent;
import org.killbill.billing.invoice.api.DefaultInvoicePaymentInfoEvent;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoicePaymentType;
import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
import org.killbill.billing.invoice.model.CreditAdjInvoiceItem;
import org.killbill.billing.invoice.model.DefaultInvoice;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.invoice.notification.NextBillingDatePoster;
import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import org.killbill.billing.util.entity.dao.EntityDaoBase;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.PersistentBus;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Ordering;
import com.google.inject.Inject;
public class DefaultInvoiceDao extends EntityDaoBase implements InvoiceDao {
private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
private static final Ordering INVOICE_MODEL_DAO_ORDERING = Ordering.natural()
.onResultOf(new Function() {
@Override
public Comparable apply(final InvoiceModelDao invoice) {
return invoice.getTargetDate() == null ? invoice.getCreatedDate().toLocalDate() : invoice.getTargetDate();
}
});
private static final Collection INVOICE_ITEM_TYPES_ADJUSTABLE = ImmutableList.of(InvoiceItemType.EXTERNAL_CHARGE,
InvoiceItemType.FIXED,
InvoiceItemType.RECURRING,
InvoiceItemType.TAX,
InvoiceItemType.USAGE,
InvoiceItemType.PARENT_SUMMARY);
private final NextBillingDatePoster nextBillingDatePoster;
private final PersistentBus eventBus;
private final InternalCallContextFactory internalCallContextFactory;
private final InvoiceDaoHelper invoiceDaoHelper;
private final CBADao cbaDao;
private final InvoiceConfig invoiceConfig;
private final Clock clock;
private final CacheControllerDispatcher cacheControllerDispatcher;
private final NonEntityDao nonEntityDao;
private final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster;
@Inject
public DefaultInvoiceDao(final IDBI dbi,
final NextBillingDatePoster nextBillingDatePoster,
final PersistentBus eventBus,
final Clock clock,
final CacheControllerDispatcher cacheControllerDispatcher,
final NonEntityDao nonEntityDao,
final InvoiceConfig invoiceConfig,
final InvoiceDaoHelper invoiceDaoHelper,
final CBADao cbaDao,
final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster,
final InternalCallContextFactory internalCallContextFactory) {
super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory), InvoiceSqlDao.class);
this.nextBillingDatePoster = nextBillingDatePoster;
this.eventBus = eventBus;
this.invoiceConfig = invoiceConfig;
this.internalCallContextFactory = internalCallContextFactory;
this.invoiceDaoHelper = invoiceDaoHelper;
this.cbaDao = cbaDao;
this.clock = clock;
this.cacheControllerDispatcher = cacheControllerDispatcher;
this.nonEntityDao = nonEntityDao;
this.parentInvoiceCommitmentPoster = parentInvoiceCommitmentPoster;
}
@Override
protected InvoiceApiException generateAlreadyExistsException(final InvoiceModelDao entity, final InternalCallContext context) {
return new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, entity.getId());
}
@Override
public List getInvoicesByAccount(final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final List invoices = ImmutableList.copyOf(INVOICE_MODEL_DAO_ORDERING.sortedCopy(Iterables.filter(invoiceSqlDao.getByAccountRecordId(context),
new Predicate() {
@Override
public boolean apply(final InvoiceModelDao invoice) {
return !invoice.isMigrated();
}
})));
invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
return invoices;
}
});
}
@Override
public List getAllInvoicesByAccount(final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
return invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
}
});
}
@Override
public List getInvoicesByAccount(final LocalDate fromDate, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final List invoices = getAllNonMigratedInvoicesByAccountAfterDate(invoiceDao, fromDate, context);
invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
return invoices;
}
});
}
private List getAllNonMigratedInvoicesByAccountAfterDate(final InvoiceSqlDao invoiceSqlDao, final LocalDate fromDate, final InternalTenantContext context) {
return ImmutableList.copyOf(INVOICE_MODEL_DAO_ORDERING.sortedCopy(Iterables.filter(invoiceSqlDao.getByAccountRecordId(context),
new Predicate() {
@Override
public boolean apply(final InvoiceModelDao invoice) {
return !invoice.isMigrated() && invoice.getTargetDate().compareTo(fromDate) >= 0;
}
})));
}
@Override
public InvoiceModelDao getById(final UUID invoiceId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() {
@Override
public InvoiceModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final InvoiceModelDao invoice = invoiceSqlDao.getById(invoiceId.toString(), context);
if (invoice == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
}
invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
return invoice;
}
});
}
@Override
public InvoiceModelDao getByNumber(final Integer number, final InternalTenantContext context) throws InvoiceApiException {
if (number == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_NUMBER, "(null)");
}
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper() {
@Override
public InvoiceModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final InvoiceModelDao invoice = invoiceDao.getByRecordId(number.longValue(), context);
if (invoice == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_NUMBER_NOT_FOUND, number.longValue());
}
// The context may not contain the account record id at this point - we couldn't do it in the API above
// as we couldn't get access to the invoice object until now.
final InternalTenantContext contextWithAccountRecordId = internalCallContextFactory.createInternalTenantContext(invoice.getAccountId(), context);
invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, contextWithAccountRecordId);
return invoice;
}
});
}
@Override
public void setFutureAccountNotificationsForEmptyInvoice(final UUID accountId, final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, accountId, callbackDateTimePerSubscriptions, context);
return null;
}
});
}
@Override
public void createInvoice(final InvoiceModelDao invoice, final List invoiceItems,
final boolean isRealInvoice, final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final InvoiceModelDao currentInvoice = transactional.getById(invoice.getId().toString(), context);
if (currentInvoice == null) {
// We only want to insert that invoice if there are real invoiceItems associated to it -- if not, this is just
// a shell invoice and we only need to insert the invoiceItems -- for the already existing invoices
if (isRealInvoice) {
transactional.create(invoice, context);
}
// Create the invoice items
final InvoiceItemSqlDao transInvoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceItems) {
createInvoiceItemFromTransaction(transInvoiceItemSqlDao, invoiceItemModelDao, context);
}
cbaDao.addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
if (InvoiceStatus.COMMITTED.equals(invoice.getStatus())) {
notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoice.getAccountId(), callbackDateTimePerSubscriptions, context);
}
if (invoice.isParentInvoice()) {
notifyOfParentInvoiceCreation(entitySqlDaoWrapperFactory, invoice, callbackDateTimePerSubscriptions, context);
}
}
return null;
}
});
}
@Override
public List createInvoices(final List invoices, final InternalCallContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final InvoiceItemSqlDao transInvoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
final List createdInvoiceItems = new LinkedList();
for (final InvoiceModelDao invoiceModelDao : invoices) {
boolean madeChanges = false;
boolean newInvoice = false;
// Create the invoice if needed
if (invoiceSqlDao.getById(invoiceModelDao.getId().toString(), context) == null) {
invoiceSqlDao.create(invoiceModelDao, context);
madeChanges = true;
newInvoice = true;
}
// Create the invoice items if needed
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceModelDao.getInvoiceItems()) {
if (transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context) == null) {
createInvoiceItemFromTransaction(transInvoiceItemSqlDao, invoiceItemModelDao, context);
createdInvoiceItems.add(transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context));
madeChanges = true;
}
}
if (madeChanges) {
cbaDao.addCBAComplexityFromTransaction(invoiceModelDao.getId(), entitySqlDaoWrapperFactory, context);
}
if (InvoiceStatus.COMMITTED.equals(invoiceModelDao.getStatus())) {
if (newInvoice) {
notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoiceModelDao, context);
} else if (madeChanges) {
// Notify the bus since the balance of the invoice changed (only if the invoice is COMMITTED)
// TODO should we post an InvoiceCreationInternalEvent event instead? Note! This will trigger a payment (see InvoiceHandler)
notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceModelDao.getId(), invoiceModelDao.getAccountId(), context.getUserToken(), context);
}
}
}
return createdInvoiceItems;
}
});
}
@Override
public List getInvoicesBySubscription(final UUID subscriptionId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final List invoices = invoiceDao.getInvoicesBySubscription(subscriptionId.toString(), context);
invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
return invoices;
}
});
}
@Override
public Pagination searchInvoices(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
Integer invoiceNumberParsed = null;
try {
invoiceNumberParsed = Integer.parseInt(searchKey);
} catch (final NumberFormatException ignored) {
}
final Integer invoiceNumber = invoiceNumberParsed;
return paginationHelper.getPagination(InvoiceSqlDao.class,
new PaginationIteratorBuilder() {
@Override
public Long getCount(final InvoiceSqlDao invoiceSqlDao, final InternalTenantContext context) {
return invoiceNumber != null ? 1L : invoiceSqlDao.getSearchCount(searchKey, String.format("%%%s%%", searchKey), context);
}
@Override
public Iterator build(final InvoiceSqlDao invoiceSqlDao, final Long limit, final InternalTenantContext context) {
try {
return invoiceNumber != null ?
ImmutableList.of(getByNumber(invoiceNumber, context)).iterator() :
invoiceSqlDao.search(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
} catch (final InvoiceApiException ignored) {
return Iterators.emptyIterator();
}
}
},
offset,
limit,
context);
}
@Override
public BigDecimal getAccountBalance(final UUID accountId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() {
@Override
public BigDecimal inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
BigDecimal cba = BigDecimal.ZERO;
BigDecimal accountBalance = BigDecimal.ZERO;
final List invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
for (final InvoiceModelDao cur : invoices) {
accountBalance = accountBalance.add((new DefaultInvoice(cur)).getBalance());
cba = cba.add(InvoiceModelDaoHelper.getCBAAmount(cur));
}
return accountBalance.subtract(cba);
}
});
}
@Override
public BigDecimal getAccountCBA(final UUID accountId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() {
@Override
public BigDecimal inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
return cbaDao.getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
}
});
}
@Override
public List getUnpaidInvoicesByAccountId(final UUID accountId, @Nullable final LocalDate upToDate, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
return invoiceDaoHelper.getUnpaidInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, upToDate, context);
}
});
}
@Override
public UUID getInvoiceIdByPaymentId(final UUID paymentId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper() {
@Override
public UUID inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
return entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getInvoiceIdByPaymentId(paymentId.toString(), context);
}
});
}
@Override
public List getInvoicePaymentsByPaymentId(final UUID paymentId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
return entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getInvoicePayments(paymentId.toString(), context);
}
});
}
@Override
public List getInvoicePaymentsByAccount(final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper>() {
@Override
public List inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
return entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getByAccountRecordId(context);
}
});
}
@Override
public InvoicePaymentModelDao createRefund(final UUID paymentId, final BigDecimal requestedRefundAmount, final boolean isInvoiceAdjusted,
final Map invoiceItemIdsWithNullAmounts, final String transactionExternalKey,
final InternalCallContext context) throws InvoiceApiException {
if (isInvoiceAdjusted && invoiceItemIdsWithNullAmounts.size() == 0) {
throw new InvoiceApiException(ErrorCode.INVOICE_ITEMS_ADJUSTMENT_MISSING);
}
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper() {
@Override
public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
final InvoiceSqlDao transInvoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final List paymentsForId = transactional.getByPaymentId(paymentId.toString(), context);
final InvoicePaymentModelDao payment = Iterables.tryFind(paymentsForId, new Predicate() {
@Override
public boolean apply(final InvoicePaymentModelDao input) {
return input.getType() == InvoicePaymentType.ATTEMPT && input.getSuccess();
}
}).orNull();
if (payment == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND, paymentId);
}
// Retrieve the amounts to adjust, if needed
final Map invoiceItemIdsWithAmounts = invoiceDaoHelper.computeItemAdjustments(payment.getInvoiceId().toString(),
entitySqlDaoWrapperFactory,
invoiceItemIdsWithNullAmounts,
context);
// Compute the actual amount to refund
final BigDecimal requestedPositiveAmount = invoiceDaoHelper.computePositiveRefundAmount(payment, requestedRefundAmount, invoiceItemIdsWithAmounts);
// Before we go further, check if that refund already got inserted -- the payment system keeps a state machine
// and so this call may be called several time for the same paymentCookieId (which is really the refundId)
final InvoicePaymentModelDao existingRefund = transactional.getPaymentForCookieId(transactionExternalKey, context);
if (existingRefund != null) {
return existingRefund;
}
final InvoicePaymentModelDao refund = new InvoicePaymentModelDao(UUIDs.randomUUID(), context.getCreatedDate(), InvoicePaymentType.REFUND,
payment.getInvoiceId(), paymentId,
context.getCreatedDate(), requestedPositiveAmount.negate(),
payment.getCurrency(), payment.getProcessedCurrency(), transactionExternalKey, payment.getId(), true);
transactional.create(refund, context);
// Retrieve invoice after the Refund
final InvoiceModelDao invoice = transInvoiceDao.getById(payment.getInvoiceId().toString(), context);
Preconditions.checkState(invoice != null, "Invoice shouldn't be null for payment " + payment.getId());
invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
final BigDecimal invoiceBalanceAfterRefund = InvoiceModelDaoHelper.getBalance(invoice);
final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
// At this point, we created the refund which made the invoice balance positive and applied any existing
// available CBA to that invoice.
// We now need to adjust the invoice and/or invoice items if needed and specified.
if (isInvoiceAdjusted) {
// Invoice item adjustment
for (final UUID invoiceItemId : invoiceItemIdsWithAmounts.keySet()) {
final BigDecimal adjAmount = invoiceItemIdsWithAmounts.get(invoiceItemId);
final InvoiceItemModelDao item = invoiceDaoHelper.createAdjustmentItem(entitySqlDaoWrapperFactory, invoice.getId(), invoiceItemId, adjAmount,
invoice.getCurrency(), context.getCreatedDate().toLocalDate(),
context);
createInvoiceItemFromTransaction(transInvoiceItemDao, item, context);
invoice.addInvoiceItem(item);
}
}
cbaDao.addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
if (isInvoiceAdjusted) {
notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoice.getId(), invoice.getAccountId(), context.getUserToken(), context);
}
notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, refund, invoice.getAccountId(), context.getUserToken(), context);
return refund;
}
});
}
@Override
public InvoicePaymentModelDao postChargeback(final UUID paymentId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper() {
@Override
public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
final List invoicePayments = transactional.getByPaymentId(paymentId.toString(), context);
final InvoicePaymentModelDao invoicePayment = Iterables.tryFind(invoicePayments, new Predicate() {
@Override
public boolean apply(final InvoicePaymentModelDao input) {
return input.getType() == InvoicePaymentType.ATTEMPT;
}
}).orNull();
if (invoicePayment == null) {
throw new InvoiceApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
}
// We expect the code to correctly pass the account currency -- the payment code, more generic accept chargeBack in different currencies,
// but this is only for direct payment (no invoice)
Preconditions.checkArgument(invoicePayment.getCurrency() == currency, String.format("Invoice payment currency %s doesn't match chargeback currency %s", invoicePayment.getCurrency(), currency));
final UUID invoicePaymentId = invoicePayment.getId();
final BigDecimal maxChargedBackAmount = invoiceDaoHelper.getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
final BigDecimal requestedChargedBackAmount = (amount == null) ? maxChargedBackAmount : amount;
if (requestedChargedBackAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new InvoiceApiException(ErrorCode.CHARGE_BACK_AMOUNT_IS_NEGATIVE);
}
if (requestedChargedBackAmount.compareTo(maxChargedBackAmount) > 0) {
throw new InvoiceApiException(ErrorCode.CHARGE_BACK_AMOUNT_TOO_HIGH, requestedChargedBackAmount, maxChargedBackAmount);
}
final InvoicePaymentModelDao payment = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getById(invoicePaymentId.toString(), context);
if (payment == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_NOT_FOUND, invoicePaymentId.toString());
}
final InvoicePaymentModelDao chargeBack = new InvoicePaymentModelDao(UUIDs.randomUUID(), context.getCreatedDate(), InvoicePaymentType.CHARGED_BACK,
payment.getInvoiceId(), payment.getPaymentId(), context.getCreatedDate(),
requestedChargedBackAmount.negate(), payment.getCurrency(), payment.getProcessedCurrency(),
chargebackTransactionExternalKey, payment.getId(), true);
transactional.create(chargeBack, context);
// Notify the bus since the balance of the invoice changed
final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargeBack.getId().toString(), context);
cbaDao.addCBAComplexityFromTransaction(payment.getInvoiceId(), entitySqlDaoWrapperFactory, context);
notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, chargeBack, accountId, context.getUserToken(), context);
return chargeBack;
}
});
}
@Override
public InvoicePaymentModelDao postChargebackReversal(final UUID paymentId, final String chargebackTransactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper() {
@Override
public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
final InvoicePaymentModelDao invoicePayment = transactional.getPaymentForCookieId(chargebackTransactionExternalKey, context);
if (invoicePayment == null) {
throw new InvoiceApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
}
transactional.updateAttempt(invoicePayment.getRecordId(),
invoicePayment.getPaymentId().toString(),
invoicePayment.getPaymentDate().toDate(),
invoicePayment.getAmount(),
invoicePayment.getCurrency(),
invoicePayment.getProcessedCurrency(),
invoicePayment.getPaymentCookieId(),
invoicePayment.getLinkedInvoicePaymentId() == null ? null : invoicePayment.getLinkedInvoicePaymentId().toString(),
false,
context);
final InvoicePaymentModelDao chargebackReversed = transactional.getByRecordId(invoicePayment.getRecordId(), context);
// Notify the bus since the balance of the invoice changed
final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargebackReversed.getId().toString(), context);
cbaDao.addCBAComplexityFromTransaction(chargebackReversed.getInvoiceId(), entitySqlDaoWrapperFactory, context);
notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, chargebackReversed, accountId, context.getUserToken(), context);
return chargebackReversed;
}
});
}
@Override
public InvoiceItemModelDao doCBAComplexity(final InvoiceModelDao invoice, final InternalCallContext context) throws InvoiceApiException {
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper() {
@Override
public InvoiceItemModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceItemModelDao cbaNewItem = cbaDao.computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context);
return cbaNewItem;
}
});
}
@Override
public Map computeItemAdjustments(final String invoiceId, final Map invoiceItemIdsWithNullAmounts, final InternalTenantContext context) throws InvoiceApiException {
return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper
© 2015 - 2025 Weber Informatics LLC | Privacy Policy