Search in sources :

Example 1 with PluginPropertySerializerException

use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.

the class PluginControlPaymentProcessor method notifyPendingPaymentOfStateChanged.

public Payment notifyPendingPaymentOfStateChanged(final boolean isApiPayment, final Account account, final UUID paymentTransactionId, final boolean isSuccess, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
    final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(paymentTransactionId, internalCallContext);
    final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionModelDao.getTransactionExternalKey(), internalCallContext);
    final PaymentAttemptModelDao attempt = Iterables.find(attempts, new Predicate<PaymentAttemptModelDao>() {

        @Override
        public boolean apply(final PaymentAttemptModelDao input) {
            return input.getTransactionId().equals(paymentTransactionId);
        }
    });
    final Iterable<PluginProperty> pluginProperties;
    try {
        pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
    } catch (final PluginPropertySerializerException e) {
        throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, String.format("Unable to deserialize payment attemptId='%s' properties", attempt.getId()));
    }
    return pluginControlledPaymentAutomatonRunner.run(isApiPayment, isSuccess, paymentTransactionModelDao.getTransactionType(), ControlOperation.NOTIFICATION_OF_STATE_CHANGE, account, attempt.getPaymentMethodId(), paymentTransactionModelDao.getPaymentId(), attempt.getPaymentExternalKey(), paymentTransactionId, paymentTransactionModelDao.getTransactionExternalKey(), paymentTransactionModelDao.getAmount(), paymentTransactionModelDao.getCurrency(), null, pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) PluginProperty(org.killbill.billing.payment.api.PluginProperty) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException)

Example 2 with PluginPropertySerializerException

use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.

the class PluginControlPaymentProcessor method retryPaymentTransaction.

public void retryPaymentTransaction(final UUID attemptId, final List<String> paymentControlPluginNames, final InternalCallContext internalCallContext) {
    final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttempt(attemptId, internalCallContext);
    log.info("Retrying attemptId='{}', paymentExternalKey='{}', transactionExternalKey='{}'. paymentControlPluginNames='{}'", attemptId, attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), paymentControlPluginNames);
    final PaymentModelDao paymentModelDao = paymentDao.getPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
    final UUID paymentId = paymentModelDao != null ? paymentModelDao.getId() : null;
    final CallContext callContext = buildCallContext(internalCallContext);
    final String transactionType = TransactionType.PURCHASE.name();
    Account account = null;
    Payment payment = null;
    PaymentTransaction paymentTransaction = null;
    try {
        account = accountInternalApi.getAccountById(attempt.getAccountId(), internalCallContext);
        final State state = paymentControlStateMachineHelper.getState(attempt.getStateName());
        final Iterable<PluginProperty> pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
        logEnterAPICall(log, transactionType, account, attempt.getPaymentMethodId(), paymentId, null, attempt.getAmount(), attempt.getCurrency(), attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), null, paymentControlPluginNames);
        payment = pluginControlledPaymentAutomatonRunner.run(state, false, attempt.getTransactionType(), ControlOperation.valueOf(attempt.getTransactionType().toString()), account, attempt.getPaymentMethodId(), paymentId, attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), // The amount on the payment attempt drives the amount of the new payment transaction
        attempt.getAmount(), attempt.getCurrency(), null, pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
        log.debug("retryPaymentTransaction result: payment='{}'", payment);
        paymentTransaction = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()), new Predicate<PaymentTransaction>() {

            @Override
            public boolean apply(final PaymentTransaction input) {
                return attempt.getTransactionExternalKey().equals(input.getExternalKey());
            }
        });
    } catch (final AccountApiException e) {
        log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
    } catch (final PaymentApiException e) {
        // Log exception unless nothing left to be paid
        if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode() && paymentControlPluginNames != null && paymentControlPluginNames.size() == 1 && InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentControlPluginNames.get(0))) {
            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames));
        } else {
            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
        }
    } catch (final PluginPropertySerializerException e) {
        log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
    } catch (final MissingEntryException e) {
        log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
    } finally {
        logExitAPICall(log, transactionType, account, payment != null ? payment.getPaymentMethodId() : null, payment != null ? payment.getId() : null, paymentTransaction != null ? paymentTransaction.getId() : null, paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null, paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null, payment != null ? payment.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null, paymentControlPluginNames, null);
    }
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) Account(org.killbill.billing.account.api.Account) PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) Predicate(com.google.common.base.Predicate) PaymentTransaction(org.killbill.billing.payment.api.PaymentTransaction) PluginProperty(org.killbill.billing.payment.api.PluginProperty) Payment(org.killbill.billing.payment.api.Payment) State(org.killbill.automaton.State) AccountApiException(org.killbill.billing.account.api.AccountApiException) MissingEntryException(org.killbill.automaton.MissingEntryException) UUID(java.util.UUID)

Example 3 with PluginPropertySerializerException

use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.

the class IncompletePaymentAttemptTask method doIteration.

// Since the code is a bit tedious to follow, I'm adding some notes here on where isApiPayment is used (valid as of 09/19/2019 - might become stale!):
// * Passed through to plugins as PaymentControlContext#isApiPayment (e.g. InvoicePaymentControlPluginApi)
// * Used in PaymentEnteringStateCallback to decide whether to send a PaymentErrorInternalEvent
// To ensure that payments are retried, isApiPayment must be true (see https://github.com/killbill/killbill/issues/880).
@VisibleForTesting
public boolean doIteration(final PaymentAttemptModelDao attempt, final boolean isApiPayment) {
    // We don't grab account lock here as the lock will be taken when calling the completeRun API.
    final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getTenantRecordId(), attempt.getAccountRecordId());
    final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(tenantContext.getTenantRecordId(), tenantContext.getAccountRecordId(), "AttemptCompletionJanitorTask", CallOrigin.INTERNAL, UserType.SYSTEM, UUIDs.randomUUID());
    final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
    final List<PaymentTransactionModelDao> filteredTransactions = ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {

        @Override
        public boolean apply(final PaymentTransactionModelDao input) {
            return input.getAttemptId().equals(attempt.getId());
        }
    }));
    // We only expect at most one transaction for a given attempt, but as a precaution we check for more; if this is the case we log a warn and continue processing the first one.
    if (filteredTransactions.size() > 1) {
        log.warn("Found {} transactions for paymentAttempt {}", filteredTransactions.size(), attempt.getId());
    }
    final PaymentTransactionModelDao transaction = filteredTransactions.isEmpty() ? null : filteredTransactions.get(0);
    if (transaction == null) {
        log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
        paymentDao.updatePaymentAttemptWithProperties(attempt.getId(), attempt.getPaymentMethodId(), attempt.getTransactionId(), "ABORTED", // Keep the initial amount, as this will drive the retries
        attempt.getAmount(), attempt.getCurrency(), attempt.getPluginProperties(), internalCallContext);
        return true;
    }
    // at which point the attempt can also be transition to a different state.
    if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
        return false;
    }
    try {
        log.info("Completing attemptId='{}', stateName='{}'", attempt.getId(), attempt.getStateName());
        final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
        final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(), isApiPayment, null, transaction.getPaymentId(), attempt.getPaymentExternalKey(), transaction.getId(), transaction.getTransactionExternalKey(), transaction.getTransactionType(), account, attempt.getPaymentMethodId(), transaction.getAmount(), transaction.getCurrency(), null, PluginPropertySerializer.deserialize(attempt.getPluginProperties()), internalCallContext, internalCallContextFactory.createCallContext(internalCallContext));
        // Normally set by leavingState Callback
        paymentStateContext.setAttemptId(attempt.getId());
        // Normally set by raw state machine
        paymentStateContext.setPaymentTransactionModelDao(transaction);
        // 
        // Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls / executePluginOnFailureCalls calls
        // to the PaymentControlPluginApi plugin and transition the state.
        // 
        pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
        return true;
    } catch (final AccountApiException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    } catch (final PluginPropertySerializerException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    } catch (final PaymentApiException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    }
    return false;
}
Also used : Account(org.killbill.billing.account.api.Account) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) InternalTenantContext(org.killbill.billing.callcontext.InternalTenantContext) AccountApiException(org.killbill.billing.account.api.AccountApiException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) Predicate(com.google.common.base.Predicate) PaymentStateControlContext(org.killbill.billing.payment.core.sm.control.PaymentStateControlContext) VisibleForTesting(com.google.common.annotations.VisibleForTesting)

Example 4 with PluginPropertySerializerException

use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.

the class IncompletePaymentAttemptTask method doIteration.

@Override
public void doIteration(final PaymentAttemptModelDao attempt) {
    // We don't grab account lock here as the lock will be taken when calling the completeRun API.
    final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getTenantRecordId(), attempt.getAccountRecordId());
    final CallContext callContext = createCallContext("AttemptCompletionJanitorTask", tenantContext);
    final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(attempt.getAccountId(), callContext);
    final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
    final List<PaymentTransactionModelDao> filteredTransactions = ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {

        @Override
        public boolean apply(final PaymentTransactionModelDao input) {
            return input.getAttemptId().equals(attempt.getId());
        }
    }));
    // We only expect at most one transaction for a given attempt, but as a precaution we check for more; if this is the case we log a warn and continue processing the first one.
    if (filteredTransactions.size() > 1) {
        log.warn("Found {} transactions for paymentAttempt {}", filteredTransactions.size(), attempt.getId());
    }
    final PaymentTransactionModelDao transaction = filteredTransactions.isEmpty() ? null : filteredTransactions.get(0);
    if (transaction == null) {
        log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
        paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
        return;
    }
    // at which point the attempt can also be transition to a different state.
    if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
        return;
    }
    try {
        log.info("Completing attemptId='{}'", attempt.getId());
        final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
        // unclear
        final boolean isApiPayment = true;
        final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(), isApiPayment, null, transaction.getPaymentId(), attempt.getPaymentExternalKey(), transaction.getId(), transaction.getTransactionExternalKey(), transaction.getTransactionType(), account, attempt.getPaymentMethodId(), transaction.getAmount(), transaction.getCurrency(), PluginPropertySerializer.deserialize(attempt.getPluginProperties()), internalCallContext, callContext);
        // Normally set by leavingState Callback
        paymentStateContext.setAttemptId(attempt.getId());
        // Normally set by raw state machine
        paymentStateContext.setPaymentTransactionModelDao(transaction);
        //
        // Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls / executePluginOnFailureCalls calls
        // to the PaymentControlPluginApi plugin and transition the state.
        //
        pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
    } catch (final AccountApiException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    } catch (final PluginPropertySerializerException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    } catch (final PaymentApiException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    }
}
Also used : Account(org.killbill.billing.account.api.Account) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) InternalTenantContext(org.killbill.billing.callcontext.InternalTenantContext) AccountApiException(org.killbill.billing.account.api.AccountApiException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) Predicate(com.google.common.base.Predicate) PaymentStateControlContext(org.killbill.billing.payment.core.sm.control.PaymentStateControlContext)

Example 5 with PluginPropertySerializerException

use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.

the class DefaultControlInitiated method leavingState.

@Override
public void leavingState(final State state) throws OperationException {
    final DateTime utcNow = pluginControlPaymentAutomatonRunner.getClock().getUTCNow();
    // Retrieve the associated payment transaction, if any
    PaymentTransactionModelDao paymentTransactionModelDaoCandidate = null;
    if (stateContext.getTransactionId() != null) {
        paymentTransactionModelDaoCandidate = paymentDao.getPaymentTransaction(stateContext.getTransactionId(), stateContext.getInternalCallContext());
        Preconditions.checkNotNull(paymentTransactionModelDaoCandidate, "paymentTransaction cannot be null for id " + stateContext.getTransactionId());
    } else if (stateContext.getPaymentTransactionExternalKey() != null) {
        final List<PaymentTransactionModelDao> paymentTransactionModelDaos = paymentDao.getPaymentTransactionsByExternalKey(stateContext.getPaymentTransactionExternalKey(), stateContext.getInternalCallContext());
        if (!paymentTransactionModelDaos.isEmpty()) {
            paymentTransactionModelDaoCandidate = paymentTransactionModelDaos.get(paymentTransactionModelDaos.size() - 1);
        }
    }
    final PaymentTransactionModelDao paymentTransactionModelDao = paymentTransactionModelDaoCandidate != null && TRANSIENT_TRANSACTION_STATUSES.contains(paymentTransactionModelDaoCandidate.getTransactionStatus()) ? paymentTransactionModelDaoCandidate : null;
    if (stateContext.getPaymentId() != null && stateContext.getPaymentExternalKey() == null) {
        final PaymentModelDao payment = paymentDao.getPayment(stateContext.getPaymentId(), stateContext.getInternalCallContext());
        Preconditions.checkNotNull(payment, "payment cannot be null for id " + stateContext.getPaymentId());
        stateContext.setPaymentExternalKey(payment.getExternalKey());
        stateContext.setPaymentMethodId(payment.getPaymentMethodId());
    } else if (stateContext.getPaymentExternalKey() == null) {
        final UUID paymentIdForNewPayment = UUIDs.randomUUID();
        stateContext.setPaymentIdForNewPayment(paymentIdForNewPayment);
        stateContext.setPaymentExternalKey(paymentIdForNewPayment.toString());
    }
    if (paymentTransactionModelDao != null) {
        stateContext.setPaymentTransactionModelDao(paymentTransactionModelDao);
        stateContext.setProcessedAmount(paymentTransactionModelDao.getProcessedAmount());
        stateContext.setProcessedCurrency(paymentTransactionModelDao.getProcessedCurrency());
    } else if (stateContext.getPaymentTransactionExternalKey() == null) {
        final UUID paymentTransactionIdForNewPaymentTransaction = UUIDs.randomUUID();
        stateContext.setPaymentTransactionIdForNewPaymentTransaction(paymentTransactionIdForNewPaymentTransaction);
        stateContext.setPaymentTransactionExternalKey(paymentTransactionIdForNewPaymentTransaction.toString());
    }
    // In this case, we also want to provide the associated plugin name
    if (stateContext.getPaymentMethodId() != null) {
        final PaymentMethodModelDao pm = paymentDao.getPaymentMethod(stateContext.getPaymentMethodId(), stateContext.getInternalCallContext());
        // Payment method was deleted
        if (pm != null) {
            stateContext.setOriginalPaymentPluginName(pm.getPluginName());
        }
    }
    if (state.getName().equals(initialState.getName()) || state.getName().equals(retriedState.getName())) {
        try {
            final PaymentAttemptModelDao attempt;
            if (paymentTransactionModelDao != null && paymentTransactionModelDao.getAttemptId() != null) {
                attempt = pluginControlPaymentAutomatonRunner.getPaymentDao().getPaymentAttempt(paymentTransactionModelDao.getAttemptId(), stateContext.getInternalCallContext());
                Preconditions.checkNotNull(attempt, "attempt cannot be null for id " + paymentTransactionModelDao.getAttemptId());
            } else {
                // 
                // We don't serialize any properties at this stage to avoid serializing sensitive information.
                // However, if after going through the control plugins, the attempt end up in RETRIED state,
                // the properties will be serialized in the enteringState callback (any plugin that sets a
                // retried date is responsible to correctly remove sensitive information such as CVV, ...)
                // 
                final byte[] serializedProperties = PluginPropertySerializer.serialize(ImmutableList.<PluginProperty>of());
                attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(), utcNow, utcNow, stateContext.getPaymentExternalKey(), stateContext.getTransactionId(), stateContext.getPaymentTransactionExternalKey(), transactionType, initialState.getName(), stateContext.getAmount(), stateContext.getCurrency(), stateContext.getPaymentControlPluginNames(), serializedProperties);
                pluginControlPaymentAutomatonRunner.getPaymentDao().insertPaymentAttemptWithProperties(attempt, stateContext.getInternalCallContext());
            }
            stateContext.setAttemptId(attempt.getId());
        } catch (final PluginPropertySerializerException e) {
            throw new OperationException(e);
        }
    }
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) PaymentMethodModelDao(org.killbill.billing.payment.dao.PaymentMethodModelDao) List(java.util.List) ImmutableList(com.google.common.collect.ImmutableList) UUID(java.util.UUID) DateTime(org.joda.time.DateTime) OperationException(org.killbill.automaton.OperationException)

Aggregations

PluginPropertySerializerException (org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException)5 PaymentApiException (org.killbill.billing.payment.api.PaymentApiException)4 PaymentTransactionModelDao (org.killbill.billing.payment.dao.PaymentTransactionModelDao)4 Predicate (com.google.common.base.Predicate)3 Account (org.killbill.billing.account.api.Account)3 AccountApiException (org.killbill.billing.account.api.AccountApiException)3 InternalCallContext (org.killbill.billing.callcontext.InternalCallContext)3 PaymentAttemptModelDao (org.killbill.billing.payment.dao.PaymentAttemptModelDao)3 UUID (java.util.UUID)2 InternalTenantContext (org.killbill.billing.callcontext.InternalTenantContext)2 PluginProperty (org.killbill.billing.payment.api.PluginProperty)2 PaymentStateControlContext (org.killbill.billing.payment.core.sm.control.PaymentStateControlContext)2 PaymentModelDao (org.killbill.billing.payment.dao.PaymentModelDao)2 CallContext (org.killbill.billing.util.callcontext.CallContext)2 VisibleForTesting (com.google.common.annotations.VisibleForTesting)1 ImmutableList (com.google.common.collect.ImmutableList)1 List (java.util.List)1 DateTime (org.joda.time.DateTime)1 MissingEntryException (org.killbill.automaton.MissingEntryException)1 OperationException (org.killbill.automaton.OperationException)1