Search in sources :

Example 36 with PaymentApiException

use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.

the class TestIntegrationParentInvoice method testUnParentingWithUnpaidInvoice.

@Test(groups = "slow")
public void testUnParentingWithUnpaidInvoice() throws Exception {
    final int billingDay = 14;
    final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
    // set clock to the initial start date
    clock.setTime(initialCreationDate);
    final Account parentAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
    Account childAccount = createAccountWithNonOsgiPaymentMethod(getChildAccountData(billingDay, parentAccount.getId(), true));
    // Verify mapping
    childAccount = accountUserApi.getAccountById(childAccount.getId(), callContext);
    assertEquals(childAccount.getParentAccountId(), parentAccount.getId());
    assertTrue(childAccount.isPaymentDelegatedToParent());
    List<Account> childrenAccounts = accountUserApi.getChildrenAccounts(parentAccount.getId(), callContext);
    assertEquals(childrenAccounts.size(), 1);
    assertEquals(childrenAccounts.get(0).getId(), childAccount.getId());
    // Create subscription
    createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
    // Moving a day the NotificationQ calls the commitInvoice. No payment is expected
    busHandler.pushExpectedEvents(NextEvent.INVOICE);
    clock.addDays(1);
    assertListenerStatus();
    // First Parent invoice over TRIAL period
    List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
    assertEquals(parentInvoices.size(), 1);
    Invoice parentInvoice = parentInvoices.get(0);
    assertEquals(parentInvoice.getNumberOfItems(), 1);
    assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
    assertTrue(parentInvoice.isParentInvoice());
    assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
    // First child invoice over TRIAL period
    List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
    assertEquals(childInvoices.size(), 1);
    assertEquals(childInvoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
    busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE);
    clock.addDays(29);
    assertListenerStatus();
    paymentPlugin.makeNextPaymentFailWithError();
    busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
    clock.addDays(1);
    assertListenerStatus();
    // Second Parent invoice over Recurring period
    parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
    assertEquals(parentInvoices.size(), 2);
    parentInvoice = parentInvoices.get(1);
    assertEquals(parentInvoice.getNumberOfItems(), 1);
    assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
    assertTrue(parentInvoice.isParentInvoice());
    assertEquals(parentInvoice.getBalance().compareTo(new BigDecimal("249.95")), 0);
    // The parent has attempted to pay the child invoice
    assertEquals(parentInvoice.getPayments().size(), 1);
    assertEquals(paymentApi.getPayment(parentInvoice.getPayments().get(0).getPaymentId(), false, false, ImmutableList.<PluginProperty>of(), callContext).getPaymentMethodId(), parentAccount.getPaymentMethodId());
    // Second child invoice over Recurring period
    childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
    assertEquals(childInvoices.size(), 2);
    assertEquals(childInvoices.get(1).getBalance().compareTo(new BigDecimal("249.95")), 0);
    // Verify balances
    assertEquals(invoiceUserApi.getAccountBalance(parentAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
    assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
    // Un-parent the child
    final AccountModelDao childAccountModelDao = new AccountModelDao(childAccount.getId(), childAccount);
    childAccountModelDao.setParentAccountId(null);
    childAccountModelDao.setIsPaymentDelegatedToParent(false);
    accountUserApi.updateAccount(new DefaultAccount(childAccountModelDao), callContext);
    // Verify mapping
    childAccount = accountUserApi.getAccountById(childAccount.getId(), callContext);
    assertNull(childAccount.getParentAccountId());
    assertFalse(childAccount.isPaymentDelegatedToParent());
    childrenAccounts = accountUserApi.getChildrenAccounts(parentAccount.getId(), callContext);
    assertEquals(childrenAccounts.size(), 0);
    // Verify balances
    // TODO Should we automatically adjust the invoice at the parent level or should it be the responsibility of the user?
    assertEquals(invoiceUserApi.getAccountBalance(parentAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
    assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
    // Verify Invoice apis getParentAccountId/getParentInvoiceId
    assertEquals(childInvoices.get(1).getParentAccountId(), parentAccount.getId());
    assertEquals(childInvoices.get(1).getParentInvoiceId(), parentInvoice.getId());
    try {
        invoicePaymentApi.createPurchaseForInvoicePayment(childAccount, childInvoices.get(1).getId(), childAccount.getPaymentMethodId(), null, childInvoices.get(1).getBalance(), childInvoices.get(1).getCurrency(), null, UUID.randomUUID().toString(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
        Assert.fail("Payment should fail, invoice belongs to parent");
    } catch (final PaymentApiException e) {
        assertEquals(ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode(), e.getCode());
    }
    final int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays(internalCallContext).get(0);
    // Move time for retry to happen
    busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
    clock.addDays(nbDaysBeforeRetry + 1);
    assertListenerStatus();
    // Second Parent invoice over Recurring period
    parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
    // Note that the parent still owns both invoices
    assertEquals(parentInvoices.size(), 2);
    parentInvoice = parentInvoices.get(1);
    assertEquals(parentInvoice.getNumberOfItems(), 1);
    assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
    assertTrue(parentInvoice.isParentInvoice());
    assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
    // Even if the child-parent mapping has been removed, the parent has retried and successfully paid the summary invoice
    // TODO Should we automatically disable payment retries when un-parenting?
    assertEquals(parentInvoice.getPayments().size(), 1);
    assertEquals(paymentApi.getPayment(parentInvoice.getPayments().get(0).getPaymentId(), false, false, ImmutableList.<PluginProperty>of(), callContext).getPaymentMethodId(), parentAccount.getPaymentMethodId());
    // Second child invoice over Recurring period
    childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
    assertEquals(childInvoices.size(), 2);
    assertEquals(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
    // Verify balances (the parent has paid the summary invoice, so the child invoice is automatically paid)
    assertEquals(invoiceUserApi.getAccountBalance(parentAccount.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
    assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
    busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
    clock.addDays(29 - nbDaysBeforeRetry - 1);
    assertListenerStatus();
    // No new invoice for the parent
    parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
    assertEquals(parentInvoices.size(), 2);
    // Third child invoice over second Recurring period
    childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
    assertEquals(childInvoices.size(), 3);
    assertEquals(childInvoices.get(2).getBalance().compareTo(BigDecimal.ZERO), 0);
    // Verify the child paid the invoice this time
    assertEquals(childInvoices.get(2).getPayments().size(), 1);
    assertEquals(paymentApi.getPayment(childInvoices.get(2).getPayments().get(0).getPaymentId(), false, false, ImmutableList.<PluginProperty>of(), callContext).getPaymentMethodId(), childAccount.getPaymentMethodId());
    // Verify balances
    assertEquals(invoiceUserApi.getAccountBalance(parentAccount.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
    assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
}
Also used : DefaultAccount(org.killbill.billing.account.api.DefaultAccount) Account(org.killbill.billing.account.api.Account) AccountModelDao(org.killbill.billing.account.dao.AccountModelDao) DefaultAccount(org.killbill.billing.account.api.DefaultAccount) Invoice(org.killbill.billing.invoice.api.Invoice) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) DateTime(org.joda.time.DateTime) BigDecimal(java.math.BigDecimal) Test(org.testng.annotations.Test)

Example 37 with PaymentApiException

use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.

the class PaymentRefresher method searchPayments.

public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final boolean withAttempts, final boolean isApiPayment, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
    final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
    final Pagination<PaymentTransactionInfoPlugin> paymentTransactionInfoPlugins;
    try {
        paymentTransactionInfoPlugins = pluginApi.searchPayments(searchKey, offset, limit, properties, tenantContext);
    } catch (final PaymentPluginApiException e) {
        throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENTS, pluginName, searchKey);
    }
    // Cannot easily stream here unfortunately, since we need to merge PaymentTransactionInfoPlugin into Payment (no order assumed)
    final Multimap<UUID, PaymentTransactionInfoPlugin> payments = HashMultimap.<UUID, PaymentTransactionInfoPlugin>create();
    for (final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin : paymentTransactionInfoPlugins) {
        if (paymentTransactionInfoPlugin.getKbPaymentId() == null) {
            // Garbage from the plugin?
            log.debug("Plugin {} returned a payment without a kbPaymentId for searchKey {}", pluginName, searchKey);
        } else {
            payments.put(paymentTransactionInfoPlugin.getKbPaymentId(), paymentTransactionInfoPlugin);
        }
    }
    final Collection<Payment> results = new LinkedList<Payment>();
    for (final UUID paymentId : payments.keys()) {
        final Payment result = toPayment(paymentId, withPluginInfo ? payments.get(paymentId) : ImmutableList.<PaymentTransactionInfoPlugin>of(), withAttempts, isApiPayment, internalTenantContext);
        if (result != null) {
            results.add(result);
        }
    }
    return new DefaultPagination<Payment>(paymentTransactionInfoPlugins, limit, results.iterator());
}
Also used : PaymentPluginApi(org.killbill.billing.payment.plugin.api.PaymentPluginApi) DefaultPayment(org.killbill.billing.payment.api.DefaultPayment) Payment(org.killbill.billing.payment.api.Payment) PaymentPluginApiException(org.killbill.billing.payment.plugin.api.PaymentPluginApiException) PaymentTransactionInfoPlugin(org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) DefaultPagination(org.killbill.billing.util.entity.DefaultPagination) UUID(java.util.UUID) LinkedList(java.util.LinkedList)

Example 38 with PaymentApiException

use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.

the class OperationCallbackBase method dispatchWithAccountLockAndTimeout.

// 
// Dispatch the Callable to the executor by first wrapping it into a CallableWithAccountLock
// The dispatcher may throw a TimeoutException, ExecutionException, or InterruptedException; those will be handled in specific
// callback to eventually throw a OperationException, that will be used to drive the state machine in the right direction.
// 
protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final String pluginNames, final DispatcherCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
    final Account account = paymentStateContext.getAccount();
    logger.debug("Dispatching plugin call for account {}", account.getExternalKey());
    try {
        final Callable<PluginDispatcherReturnType<OperationResult>> task = new CallableWithAccountLock<OperationResult, ExceptionType>(locker, account.getId(), paymentConfig, callback);
        final OperationResult operationResult = PaymentPluginDispatcher.dispatchWithExceptionHandling(account, pluginNames, task, paymentPluginDispatcher);
        return operationResult;
    } catch (final PaymentApiException e) {
        throw unwrapExceptionFromDispatchedTask(e);
    }
}
Also used : Account(org.killbill.billing.account.api.Account) PluginDispatcherReturnType(org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType) CallableWithAccountLock(org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock) OperationResult(org.killbill.automaton.OperationResult) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException)

Example 39 with PaymentApiException

use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.

the class PaymentAutomatonRunner method runStateMachineOperation.

private void runStateMachineOperation(final String initialStateName, final TransactionType transactionType, final LeavingStateCallback leavingStateCallback, final OperationCallback operationCallback, final EnteringStateCallback enteringStateCallback, final Boolean includeDeletedPaymentMethod, final PaymentStateContext paymentStateContext, final PaymentAutomatonDAOHelper daoHelper) throws PaymentApiException {
    try {
        final StateMachineConfig stateMachineConfig = paymentSMHelper.getStateMachineConfig(daoHelper.getPaymentProviderPluginName(includeDeletedPaymentMethod), paymentStateContext.getInternalCallContext());
        final StateMachine initialStateMachine = stateMachineConfig.getStateMachineForState(initialStateName);
        final State initialState = initialStateMachine.getState(initialStateName);
        final Operation operation = paymentSMHelper.getOperationForTransaction(stateMachineConfig, transactionType);
        initialState.runOperation(operation, operationCallback, enteringStateCallback, leavingStateCallback);
    } catch (final MissingEntryException e) {
        throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INVALID_OPERATION, transactionType, initialStateName);
    } catch (final OperationException e) {
        if (e.getCause() == null) {
            throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
        } else if (e.getCause() instanceof PaymentApiException) {
            throw (PaymentApiException) e.getCause();
        } else {
            throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
        }
    }
}
Also used : StateMachine(org.killbill.automaton.StateMachine) State(org.killbill.automaton.State) StateMachineConfig(org.killbill.automaton.StateMachineConfig) MissingEntryException(org.killbill.automaton.MissingEntryException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) PurchaseOperation(org.killbill.billing.payment.core.sm.payments.PurchaseOperation) RefundOperation(org.killbill.billing.payment.core.sm.payments.RefundOperation) AuthorizeOperation(org.killbill.billing.payment.core.sm.payments.AuthorizeOperation) VoidOperation(org.killbill.billing.payment.core.sm.payments.VoidOperation) Operation(org.killbill.automaton.Operation) CreditOperation(org.killbill.billing.payment.core.sm.payments.CreditOperation) ChargebackOperation(org.killbill.billing.payment.core.sm.payments.ChargebackOperation) CaptureOperation(org.killbill.billing.payment.core.sm.payments.CaptureOperation) OperationException(org.killbill.automaton.OperationException)

Example 40 with PaymentApiException

use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.

the class PluginControlPaymentAutomatonRunner method run.

public Payment run(final State state, final boolean isApiPayment, final Boolean isSuccess, final TransactionType transactionType, final ControlOperation controlOperation, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, @Nullable final UUID transactionId, final String paymentTransactionExternalKey, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final DateTime effectiveDate, final Iterable<PluginProperty> properties, @Nullable final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
    final PaymentStateControlContext paymentStateContext = createContext(isApiPayment, isSuccess, transactionType, account, paymentMethodId, paymentId, paymentExternalKey, transactionId, paymentTransactionExternalKey, amount, currency, effectiveDate, properties, paymentControlPluginNames, callContext, internalCallContext);
    try {
        final OperationCallback callback = createOperationCallback(controlOperation, paymentStateContext);
        final LeavingStateCallback leavingStateCallback = new DefaultControlInitiated(this, paymentStateContext, paymentDao, paymentControlStateMachineHelper.getInitialState(), paymentControlStateMachineHelper.getRetriedState(), transactionType);
        final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
        state.runOperation(paymentControlStateMachineHelper.getOperation(), callback, enteringStateCallback, leavingStateCallback);
    } catch (final MissingEntryException e) {
        throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
    } catch (final OperationException e) {
        if (e.getCause() instanceof PaymentApiException) {
            throw (PaymentApiException) e.getCause();
        // If the control plugin tries to pass us back a PaymentApiException we throw it
        } else if (e.getCause() instanceof PaymentControlApiException && e.getCause().getCause() instanceof PaymentApiException) {
            throw (PaymentApiException) e.getCause().getCause();
        } else if (e.getCause() != null || paymentStateContext.getResult() == null) {
            throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
        }
    }
    // we don't throw, and return the failed Payment instead to be consistent with what happens when we don't go through control api.
    return paymentStateContext.getResult();
}
Also used : DefaultControlInitiated(org.killbill.billing.payment.core.sm.control.DefaultControlInitiated) OperationCallback(org.killbill.automaton.Operation.OperationCallback) DefaultControlCompleted(org.killbill.billing.payment.core.sm.control.DefaultControlCompleted) MissingEntryException(org.killbill.automaton.MissingEntryException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) LeavingStateCallback(org.killbill.automaton.State.LeavingStateCallback) EnteringStateCallback(org.killbill.automaton.State.EnteringStateCallback) OperationException(org.killbill.automaton.OperationException) PaymentControlApiException(org.killbill.billing.control.plugin.api.PaymentControlApiException) PaymentStateControlContext(org.killbill.billing.payment.core.sm.control.PaymentStateControlContext)

Aggregations

PaymentApiException (org.killbill.billing.payment.api.PaymentApiException)57 UUID (java.util.UUID)20 Test (org.testng.annotations.Test)19 PaymentTransactionModelDao (org.killbill.billing.payment.dao.PaymentTransactionModelDao)14 Account (org.killbill.billing.account.api.Account)12 Payment (org.killbill.billing.payment.api.Payment)12 PaymentAttemptModelDao (org.killbill.billing.payment.dao.PaymentAttemptModelDao)11 BigDecimal (java.math.BigDecimal)10 OperationException (org.killbill.automaton.OperationException)10 PaymentModelDao (org.killbill.billing.payment.dao.PaymentModelDao)9 Predicate (com.google.common.base.Predicate)7 PaymentControlApiException (org.killbill.billing.control.plugin.api.PaymentControlApiException)7 PluginDispatcherReturnType (org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType)7 PaymentPluginApi (org.killbill.billing.payment.plugin.api.PaymentPluginApi)7 LinkedList (java.util.LinkedList)6 AccountApiException (org.killbill.billing.account.api.AccountApiException)6 Invoice (org.killbill.billing.invoice.api.Invoice)6 PluginProperty (org.killbill.billing.payment.api.PluginProperty)6 PaymentPluginApiException (org.killbill.billing.payment.plugin.api.PaymentPluginApiException)6 DateTime (org.joda.time.DateTime)5